1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2012, 2013 Zimbra Software, LLC.
  5  * 
  6  * The contents of this file are subject to the Zimbra Public License
  7  * Version 1.4 ("License"); you may not use this file except in
  8  * compliance with the License.  You may obtain a copy of the License at
  9  * http://www.zimbra.com/license.
 10  * 
 11  * Software distributed under the License is distributed on an "AS IS"
 12  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 13  * ***** END LICENSE BLOCK *****
 14  */
 15 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
 16 (function(win) {
 17 	var whiteSpaceRe = /^\s*|\s*$/g,
 18 		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
 19 
 20 	var tinymce = {
 21 		majorVersion : '3',
 22 
 23 		minorVersion : '5.4.1',
 24 
 25 		releaseDate : '2012-06-24',
 26 
 27 		_init : function() {
 28 			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
 29 
 30 			t.isOpera = win.opera && opera.buildNumber;
 31 
 32 			t.isWebKit = /WebKit/.test(ua);
 33 
 34 			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
 35 
 36 			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
 37 
 38 			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
 39 
 40 			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
 41 
 42 			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
 43 
 44 			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
 45 
 46 			t.isMac = ua.indexOf('Mac') != -1;
 47 
 48 			t.isAir = /adobeair/i.test(ua);
 49 
 50 			t.isIDevice = /(iPad|iPhone)/.test(ua);
 51 			
 52 			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
 53 
 54 			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
 55 			if (win.tinyMCEPreInit) {
 56 				t.suffix = tinyMCEPreInit.suffix;
 57 				t.baseURL = tinyMCEPreInit.base;
 58 				t.query = tinyMCEPreInit.query;
 59 				return;
 60 			}
 61 
 62 			// Get suffix and base
 63 			t.suffix = '';
 64 
 65 			// If base element found, add that infront of baseURL
 66 			nl = d.getElementsByTagName('base');
 67 			for (i=0; i<nl.length; i++) {
 68 				v = nl[i].href;
 69 				if (v) {
 70 					// Host only value like http://site.com or http://site.com:8008
 71 					if (/^https?:\/\/[^\/]+$/.test(v))
 72 						v += '/';
 73 
 74 					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
 75 				}
 76 			}
 77 
 78 			function getBase(n) {
 79 				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
 80 					if (/_(src|dev)\.js/g.test(n.src))
 81 						t.suffix = '_src';
 82 
 83 					if ((p = n.src.indexOf('?')) != -1)
 84 						t.query = n.src.substring(p + 1);
 85 
 86 					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
 87 
 88 					// If path to script is relative and a base href was found add that one infront
 89 					// the src property will always be an absolute one on non IE browsers and IE 8
 90 					// so this logic will basically only be executed on older IE versions
 91 					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
 92 						t.baseURL = base + t.baseURL;
 93 
 94 					return t.baseURL;
 95 				}
 96 
 97 				return null;
 98 			};
 99 
100 			// Check document
101 			nl = d.getElementsByTagName('script');
102 			for (i=0; i<nl.length; i++) {
103 				if (getBase(nl[i]))
104 					return;
105 			}
106 
107 			// Check head
108 			n = d.getElementsByTagName('head')[0];
109 			if (n) {
110 				nl = n.getElementsByTagName('script');
111 				for (i=0; i<nl.length; i++) {
112 					if (getBase(nl[i]))
113 						return;
114 				}
115 			}
116 
117 			return;
118 		},
119 
120 		is : function(o, t) {
121 			if (!t)
122 				return o !== undef;
123 
124 			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
125 				return true;
126 
127 			return typeof(o) == t;
128 		},
129 
130 		makeMap : function(items, delim, map) {
131 			var i;
132 
133 			items = items || [];
134 			delim = delim || ',';
135 
136 			if (typeof(items) == "string")
137 				items = items.split(delim);
138 
139 			map = map || {};
140 
141 			i = items.length;
142 			while (i--)
143 				map[items[i]] = {};
144 
145 			return map;
146 		},
147 
148 		each : function(o, cb, s) {
149 			var n, l;
150 
151 			if (!o)
152 				return 0;
153 
154 			s = s || o;
155 
156 			if (o.length !== undef) {
157 				// Indexed arrays, needed for Safari
158 				for (n=0, l = o.length; n < l; n++) {
159 					if (cb.call(s, o[n], n, o) === false)
160 						return 0;
161 				}
162 			} else {
163 				// Hashtables
164 				for (n in o) {
165 					if (o.hasOwnProperty(n)) {
166 						if (cb.call(s, o[n], n, o) === false)
167 							return 0;
168 					}
169 				}
170 			}
171 
172 			return 1;
173 		},
174 
175 
176 		map : function(a, f) {
177 			var o = [];
178 
179 			tinymce.each(a, function(v) {
180 				o.push(f(v));
181 			});
182 
183 			return o;
184 		},
185 
186 		grep : function(a, f) {
187 			var o = [];
188 
189 			tinymce.each(a, function(v) {
190 				if (!f || f(v))
191 					o.push(v);
192 			});
193 
194 			return o;
195 		},
196 
197 		inArray : function(a, v) {
198 			var i, l;
199 
200 			if (a) {
201 				for (i = 0, l = a.length; i < l; i++) {
202 					if (a[i] === v)
203 						return i;
204 				}
205 			}
206 
207 			return -1;
208 		},
209 
210 		extend : function(obj, ext) {
211 			var i, l, name, args = arguments, value;
212 
213 			for (i = 1, l = args.length; i < l; i++) {
214 				ext = args[i];
215 				for (name in ext) {
216 					if (ext.hasOwnProperty(name)) {
217 						value = ext[name];
218 
219 						if (value !== undef) {
220 							obj[name] = value;
221 						}
222 					}
223 				}
224 			}
225 
226 			return obj;
227 		},
228 
229 
230 		trim : function(s) {
231 			return (s ? '' + s : '').replace(whiteSpaceRe, '');
232 		},
233 
234 		create : function(s, p, root) {
235 			var t = this, sp, ns, cn, scn, c, de = 0;
236 
237 			// Parse : <prefix> <class>:<super class>
238 			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
239 			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
240 
241 			// Create namespace for new class
242 			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
243 
244 			// Class already exists
245 			if (ns[cn])
246 				return;
247 
248 			// Make pure static class
249 			if (s[2] == 'static') {
250 				ns[cn] = p;
251 
252 				if (this.onCreate)
253 					this.onCreate(s[2], s[3], ns[cn]);
254 
255 				return;
256 			}
257 
258 			// Create default constructor
259 			if (!p[cn]) {
260 				p[cn] = function() {};
261 				de = 1;
262 			}
263 
264 			// Add constructor and methods
265 			ns[cn] = p[cn];
266 			t.extend(ns[cn].prototype, p);
267 
268 			// Extend
269 			if (s[5]) {
270 				sp = t.resolve(s[5]).prototype;
271 				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
272 
273 				// Extend constructor
274 				c = ns[cn];
275 				if (de) {
276 					// Add passthrough constructor
277 					ns[cn] = function() {
278 						return sp[scn].apply(this, arguments);
279 					};
280 				} else {
281 					// Add inherit constructor
282 					ns[cn] = function() {
283 						this.parent = sp[scn];
284 						return c.apply(this, arguments);
285 					};
286 				}
287 				ns[cn].prototype[cn] = ns[cn];
288 
289 				// Add super methods
290 				t.each(sp, function(f, n) {
291 					ns[cn].prototype[n] = sp[n];
292 				});
293 
294 				// Add overridden methods
295 				t.each(p, function(f, n) {
296 					// Extend methods if needed
297 					if (sp[n]) {
298 						ns[cn].prototype[n] = function() {
299 							this.parent = sp[n];
300 							return f.apply(this, arguments);
301 						};
302 					} else {
303 						if (n != cn)
304 							ns[cn].prototype[n] = f;
305 					}
306 				});
307 			}
308 
309 			// Add static methods
310 			t.each(p['static'], function(f, n) {
311 				ns[cn][n] = f;
312 			});
313 
314 			if (this.onCreate)
315 				this.onCreate(s[2], s[3], ns[cn].prototype);
316 		},
317 
318 		walk : function(o, f, n, s) {
319 			s = s || this;
320 
321 			if (o) {
322 				if (n)
323 					o = o[n];
324 
325 				tinymce.each(o, function(o, i) {
326 					if (f.call(s, o, i, n) === false)
327 						return false;
328 
329 					tinymce.walk(o, f, n, s);
330 				});
331 			}
332 		},
333 
334 		createNS : function(n, o) {
335 			var i, v;
336 
337 			o = o || win;
338 
339 			n = n.split('.');
340 			for (i=0; i<n.length; i++) {
341 				v = n[i];
342 
343 				if (!o[v])
344 					o[v] = {};
345 
346 				o = o[v];
347 			}
348 
349 			return o;
350 		},
351 
352 		resolve : function(n, o) {
353 			var i, l;
354 
355 			o = o || win;
356 
357 			n = n.split('.');
358 			for (i = 0, l = n.length; i < l; i++) {
359 				o = o[n[i]];
360 
361 				if (!o)
362 					break;
363 			}
364 
365 			return o;
366 		},
367 
368 		addUnload : function(f, s) {
369 			var t = this, unload;
370 
371 			unload = function() {
372 				var li = t.unloads, o, n;
373 
374 				if (li) {
375 					// Call unload handlers
376 					for (n in li) {
377 						o = li[n];
378 
379 						if (o && o.func)
380 							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
381 					}
382 
383 					// Detach unload function
384 					if (win.detachEvent) {
385 						win.detachEvent('onbeforeunload', fakeUnload);
386 						win.detachEvent('onunload', unload);
387 					} else if (win.removeEventListener)
388 						win.removeEventListener('unload', unload, false);
389 
390 					// Destroy references
391 					t.unloads = o = li = w = unload = 0;
392 
393 					// Run garbarge collector on IE
394 					if (win.CollectGarbage)
395 						CollectGarbage();
396 				}
397 			};
398 
399 			function fakeUnload() {
400 				var d = document;
401 
402 				function stop() {
403 					// Prevent memory leak
404 					d.detachEvent('onstop', stop);
405 
406 					// Call unload handler
407 					if (unload)
408 						unload();
409 
410 					d = 0;
411 				};
412 
413 				// Is there things still loading, then do some magic
414 				if (d.readyState == 'interactive') {
415 					// Fire unload when the currently loading page is stopped
416 					if (d)
417 						d.attachEvent('onstop', stop);
418 
419 					// Remove onstop listener after a while to prevent the unload function
420 					// to execute if the user presses cancel in an onbeforeunload
421 					// confirm dialog and then presses the browser stop button
422 					win.setTimeout(function() {
423 						if (d)
424 							d.detachEvent('onstop', stop);
425 					}, 0);
426 				}
427 			};
428 
429 			f = {func : f, scope : s || this};
430 
431 			if (!t.unloads) {
432 				// Attach unload handler
433 				if (win.attachEvent) {
434 					win.attachEvent('onunload', unload);
435 					win.attachEvent('onbeforeunload', fakeUnload);
436 				} else if (win.addEventListener)
437 					win.addEventListener('unload', unload, false);
438 
439 				// Setup initial unload handler array
440 				t.unloads = [f];
441 			} else
442 				t.unloads.push(f);
443 
444 			return f;
445 		},
446 
447 		removeUnload : function(f) {
448 			var u = this.unloads, r = null;
449 
450 			tinymce.each(u, function(o, i) {
451 				if (o && o.func == f) {
452 					u.splice(i, 1);
453 					r = f;
454 					return false;
455 				}
456 			});
457 
458 			return r;
459 		},
460 
461 		explode : function(s, d) {
462 			if (!s || tinymce.is(s, 'array')) {
463 				return s;
464 			}
465 
466 			return tinymce.map(s.split(d || ','), tinymce.trim);
467 		},
468 
469 		_addVer : function(u) {
470 			var v;
471 
472 			if (!this.query)
473 				return u;
474 
475 			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
476 
477 			if (u.indexOf('#') == -1)
478 				return u + v;
479 
480 			return u.replace('#', v + '#');
481 		},
482 
483 		// Fix function for IE 9 where regexps isn't working correctly
484 		// Todo: remove me once MS fixes the bug
485 		_replace : function(find, replace, str) {
486 			// On IE9 we have to fake $x replacement
487 			if (isRegExpBroken) {
488 				return str.replace(find, function() {
489 					var val = replace, args = arguments, i;
490 
491 					for (i = 0; i < args.length - 2; i++) {
492 						if (args[i] === undef) {
493 							val = val.replace(new RegExp('\\$' + i, 'g'), '');
494 						} else {
495 							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
496 						}
497 					}
498 
499 					return val;
500 				});
501 			}
502 
503 			return str.replace(find, replace);
504 		}
505 
506 		};
507 
508 	// Initialize the API
509 	tinymce._init();
510 
511 	// Expose tinymce namespace to the global namespace (window)
512 	win.tinymce = win.tinyMCE = tinymce;
513 
514 	// Describe the different namespaces
515 
516 	})(window);
517 
518 
519 
520 tinymce.create('tinymce.util.Dispatcher', {
521 	scope : null,
522 	listeners : null,
523 	inDispatch: false,
524 
525 	Dispatcher : function(scope) {
526 		this.scope = scope || this;
527 		this.listeners = [];
528 	},
529 
530 	add : function(callback, scope) {
531 		this.listeners.push({cb : callback, scope : scope || this.scope});
532 
533 		return callback;
534 	},
535 
536 	addToTop : function(callback, scope) {
537 		var self = this, listener = {cb : callback, scope : scope || self.scope};
538 
539 		// Create new listeners if addToTop is executed in a dispatch loop
540 		if (self.inDispatch) {
541 			self.listeners = [listener].concat(self.listeners);
542 		} else {
543 			self.listeners.unshift(listener);
544 		}
545 
546 		return callback;
547 	},
548 
549 	remove : function(callback) {
550 		var listeners = this.listeners, output = null;
551 
552 		tinymce.each(listeners, function(listener, i) {
553 			if (callback == listener.cb) {
554 				output = listener;
555 				listeners.splice(i, 1);
556 				return false;
557 			}
558 		});
559 
560 		return output;
561 	},
562 
563 	dispatch : function() {
564 		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
565 
566 		self.inDispatch = true;
567 		
568 		// Needs to be a real loop since the listener count might change while looping
569 		// And this is also more efficient
570 		for (i = 0; i < listeners.length; i++) {
571 			listener = listeners[i];
572 			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
573 
574 			if (returnValue === false)
575 				break;
576 		}
577 
578 		self.inDispatch = false;
579 
580 		return returnValue;
581 	}
582 
583 	});
584 
585 (function() {
586 	var each = tinymce.each;
587 
588 	tinymce.create('tinymce.util.URI', {
589 		URI : function(u, s) {
590 			var t = this, o, a, b, base_url;
591 
592 			// Trim whitespace
593 			u = tinymce.trim(u);
594 
595 			// Default settings
596 			s = t.settings = s || {};
597 
598 			// Strange app protocol that isn't http/https or local anchor
599 			// For example: mailto,skype,tel etc.
600 			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
601 				t.source = u;
602 				return;
603 			}
604 
605 			// Absolute path with no host, fake host and protocol
606 			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
607 				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
608 
609 			// Relative path http:// or protocol relative //path
610 			if (!/^[\w\-]*:?\/\//.test(u)) {
611 				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
612 				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
613 			}
614 
615 			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
616 			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
617 			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
618 			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
619 				var s = u[i];
620 
621 				// Zope 3 workaround, they use @@something
622 				if (s)
623 					s = s.replace(/\(mce_at\)/g, '@@');
624 
625 				t[v] = s;
626 			});
627 
628 			b = s.base_uri;
629 			if (b) {
630 				if (!t.protocol)
631 					t.protocol = b.protocol;
632 
633 				if (!t.userInfo)
634 					t.userInfo = b.userInfo;
635 
636 				if (!t.port && t.host === 'mce_host')
637 					t.port = b.port;
638 
639 				if (!t.host || t.host === 'mce_host')
640 					t.host = b.host;
641 
642 				t.source = '';
643 			}
644 
645 			//t.path = t.path || '/';
646 		},
647 
648 		setPath : function(p) {
649 			var t = this;
650 
651 			p = /^(.*?)\/?(\w+)?$/.exec(p);
652 
653 			// Update path parts
654 			t.path = p[0];
655 			t.directory = p[1];
656 			t.file = p[2];
657 
658 			// Rebuild source
659 			t.source = '';
660 			t.getURI();
661 		},
662 
663 		toRelative : function(u) {
664 			var t = this, o;
665 
666 			if (u === "./")
667 				return u;
668 
669 			u = new tinymce.util.URI(u, {base_uri : t});
670 
671 			// Not on same domain/port or protocol
672 			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
673 				return u.getURI();
674 
675 			var tu = t.getURI(), uu = u.getURI();
676 			
677 			// Allow usage of the base_uri when relative_urls = true
678 			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
679 				return tu;
680 
681 			o = t.toRelPath(t.path, u.path);
682 
683 			// Add query
684 			if (u.query)
685 				o += '?' + u.query;
686 
687 			// Add anchor
688 			if (u.anchor)
689 				o += '#' + u.anchor;
690 
691 			return o;
692 		},
693 	
694 		toAbsolute : function(u, nh) {
695 			u = new tinymce.util.URI(u, {base_uri : this});
696 
697 			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
698 		},
699 
700 		toRelPath : function(base, path) {
701 			var items, bp = 0, out = '', i, l;
702 
703 			// Split the paths
704 			base = base.substring(0, base.lastIndexOf('/'));
705 			base = base.split('/');
706 			items = path.split('/');
707 
708 			if (base.length >= items.length) {
709 				for (i = 0, l = base.length; i < l; i++) {
710 					if (i >= items.length || base[i] != items[i]) {
711 						bp = i + 1;
712 						break;
713 					}
714 				}
715 			}
716 
717 			if (base.length < items.length) {
718 				for (i = 0, l = items.length; i < l; i++) {
719 					if (i >= base.length || base[i] != items[i]) {
720 						bp = i + 1;
721 						break;
722 					}
723 				}
724 			}
725 
726 			if (bp === 1)
727 				return path;
728 
729 			for (i = 0, l = base.length - (bp - 1); i < l; i++)
730 				out += "../";
731 
732 			for (i = bp - 1, l = items.length; i < l; i++) {
733 				if (i != bp - 1)
734 					out += "/" + items[i];
735 				else
736 					out += items[i];
737 			}
738 
739 			return out;
740 		},
741 
742 		toAbsPath : function(base, path) {
743 			var i, nb = 0, o = [], tr, outPath;
744 
745 			// Split paths
746 			tr = /\/$/.test(path) ? '/' : '';
747 			base = base.split('/');
748 			path = path.split('/');
749 
750 			// Remove empty chunks
751 			each(base, function(k) {
752 				if (k)
753 					o.push(k);
754 			});
755 
756 			base = o;
757 
758 			// Merge relURLParts chunks
759 			for (i = path.length - 1, o = []; i >= 0; i--) {
760 				// Ignore empty or .
761 				if (path[i].length === 0 || path[i] === ".")
762 					continue;
763 
764 				// Is parent
765 				if (path[i] === '..') {
766 					nb++;
767 					continue;
768 				}
769 
770 				// Move up
771 				if (nb > 0) {
772 					nb--;
773 					continue;
774 				}
775 
776 				o.push(path[i]);
777 			}
778 
779 			i = base.length - nb;
780 
781 			// If /a/b/c or /
782 			if (i <= 0)
783 				outPath = o.reverse().join('/');
784 			else
785 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
786 
787 			// Add front / if it's needed
788 			if (outPath.indexOf('/') !== 0)
789 				outPath = '/' + outPath;
790 
791 			// Add traling / if it's needed
792 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
793 				outPath += tr;
794 
795 			return outPath;
796 		},
797 
798 		getURI : function(nh) {
799 			var s, t = this;
800 
801 			// Rebuild source
802 			if (!t.source || nh) {
803 				s = '';
804 
805 				if (!nh) {
806 					if (t.protocol)
807 						s += t.protocol + '://';
808 
809 					if (t.userInfo)
810 						s += t.userInfo + '@';
811 
812 					if (t.host)
813 						s += t.host;
814 
815 					if (t.port)
816 						s += ':' + t.port;
817 				}
818 
819 				if (t.path)
820 					s += t.path;
821 
822 				if (t.query)
823 					s += '?' + t.query;
824 
825 				if (t.anchor)
826 					s += '#' + t.anchor;
827 
828 				t.source = s;
829 			}
830 
831 			return t.source;
832 		}
833 	});
834 })();
835 
836 (function() {
837 	var each = tinymce.each;
838 
839 	tinymce.create('static tinymce.util.Cookie', {
840 		getHash : function(n) {
841 			var v = this.get(n), h;
842 
843 			if (v) {
844 				each(v.split('&'), function(v) {
845 					v = v.split('=');
846 					h = h || {};
847 					h[unescape(v[0])] = unescape(v[1]);
848 				});
849 			}
850 
851 			return h;
852 		},
853 
854 		setHash : function(n, v, e, p, d, s) {
855 			var o = '';
856 
857 			each(v, function(v, k) {
858 				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
859 			});
860 
861 			this.set(n, o, e, p, d, s);
862 		},
863 
864 		get : function(n) {
865 			var c = document.cookie, e, p = n + "=", b;
866 
867 			// Strict mode
868 			if (!c)
869 				return;
870 
871 			b = c.indexOf("; " + p);
872 
873 			if (b == -1) {
874 				b = c.indexOf(p);
875 
876 				if (b !== 0)
877 					return null;
878 			} else
879 				b += 2;
880 
881 			e = c.indexOf(";", b);
882 
883 			if (e == -1)
884 				e = c.length;
885 
886 			return unescape(c.substring(b + p.length, e));
887 		},
888 
889 		set : function(n, v, e, p, d, s) {
890 			document.cookie = n + "=" + escape(v) +
891 				((e) ? "; expires=" + e.toGMTString() : "") +
892 				((p) ? "; path=" + escape(p) : "") +
893 				((d) ? "; domain=" + d : "") +
894 				((s) ? "; secure" : "");
895 		},
896 
897 		remove : function(name, path, domain) {
898 			var date = new Date();
899 
900 			date.setTime(date.getTime() - 1000);
901 
902 			this.set(name, '', date, path, domain);
903 		}
904 	});
905 })();
906 
907 (function() {
908 	function serialize(o, quote) {
909 		var i, v, t, name;
910 
911 		quote = quote || '"';
912 
913 		if (o == null)
914 			return 'null';
915 
916 		t = typeof o;
917 
918 		if (t == 'string') {
919 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
920 
921 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
922 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
923 				if (quote === '"' && a === "'")
924 					return a;
925 
926 				i = v.indexOf(b);
927 
928 				if (i + 1)
929 					return '\\' + v.charAt(i + 1);
930 
931 				a = b.charCodeAt().toString(16);
932 
933 				return '\\u' + '0000'.substring(a.length) + a;
934 			}) + quote;
935 		}
936 
937 		if (t == 'object') {
938 			if (o.hasOwnProperty && o instanceof Array) {
939 					for (i=0, v = '['; i<o.length; i++)
940 						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
941 
942 					return v + ']';
943 				}
944 
945 				v = '{';
946 
947 				for (name in o) {
948 					if (o.hasOwnProperty(name)) {
949 						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
950 					}
951 				}
952 
953 				return v + '}';
954 		}
955 
956 		return '' + o;
957 	};
958 
959 	tinymce.util.JSON = {
960 		serialize: serialize,
961 
962 		parse: function(s) {
963 			try {
964 				return eval('(' + s + ')');
965 			} catch (ex) {
966 				// Ignore
967 			}
968 		}
969 
970 		};
971 })();
972 
973 tinymce.create('static tinymce.util.XHR', {
974 	send : function(o) {
975 		var x, t, w = window, c = 0;
976 
977 		function ready() {
978 			if (!o.async || x.readyState == 4 || c++ > 10000) {
979 				if (o.success && c < 10000 && x.status == 200)
980 					o.success.call(o.success_scope, '' + x.responseText, x, o);
981 				else if (o.error)
982 					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
983 
984 				x = null;
985 			} else
986 				w.setTimeout(ready, 10);
987 		};
988 
989 		// Default settings
990 		o.scope = o.scope || this;
991 		o.success_scope = o.success_scope || o.scope;
992 		o.error_scope = o.error_scope || o.scope;
993 		o.async = o.async === false ? false : true;
994 		o.data = o.data || '';
995 
996 		function get(s) {
997 			x = 0;
998 
999 			try {
1000 				x = new ActiveXObject(s);
1001 			} catch (ex) {
1002 			}
1003 
1004 			return x;
1005 		};
1006 
1007 		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1008 
1009 		if (x) {
1010 			if (x.overrideMimeType)
1011 				x.overrideMimeType(o.content_type);
1012 
1013 			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1014 
1015 			if (o.content_type)
1016 				x.setRequestHeader('Content-Type', o.content_type);
1017 
1018 			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1019 
1020 			x.send(o.data);
1021 
1022 			// Syncronous request
1023 			if (!o.async)
1024 				return ready();
1025 
1026 			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1027 			t = w.setTimeout(ready, 10);
1028 		}
1029 	}
1030 });
1031 
1032 (function() {
1033 	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1034 
1035 	tinymce.create('tinymce.util.JSONRequest', {
1036 		JSONRequest : function(s) {
1037 			this.settings = extend({
1038 			}, s);
1039 			this.count = 0;
1040 		},
1041 
1042 		send : function(o) {
1043 			var ecb = o.error, scb = o.success;
1044 
1045 			o = extend(this.settings, o);
1046 
1047 			o.success = function(c, x) {
1048 				c = JSON.parse(c);
1049 
1050 				if (typeof(c) == 'undefined') {
1051 					c = {
1052 						error : 'JSON Parse error.'
1053 					};
1054 				}
1055 
1056 				if (c.error)
1057 					ecb.call(o.error_scope || o.scope, c.error, x);
1058 				else
1059 					scb.call(o.success_scope || o.scope, c.result);
1060 			};
1061 
1062 			o.error = function(ty, x) {
1063 				if (ecb)
1064 					ecb.call(o.error_scope || o.scope, ty, x);
1065 			};
1066 
1067 			o.data = JSON.serialize({
1068 				id : o.id || 'c' + (this.count++),
1069 				method : o.method,
1070 				params : o.params
1071 			});
1072 
1073 			// JSON content type for Ruby on rails. Bug: #1883287
1074 			o.content_type = 'application/json';
1075 
1076 			XHR.send(o);
1077 		},
1078 
1079 		'static' : {
1080 			sendRPC : function(o) {
1081 				return new tinymce.util.JSONRequest().send(o);
1082 			}
1083 		}
1084 	});
1085 }());
1086 (function(tinymce){
1087 	tinymce.VK = {
1088 		BACKSPACE: 8,
1089 		DELETE: 46,
1090 		DOWN: 40,
1091 		ENTER: 13,
1092 		LEFT: 37,
1093 		RIGHT: 39,
1094 		SPACEBAR: 32,
1095 		TAB: 9,
1096 		UP: 38,
1097 
1098 		modifierPressed: function (e) {
1099 			return e.shiftKey || e.ctrlKey || e.altKey;
1100 		},
1101 
1102 		metaKeyPressed: function(e) {
1103 			return tinymce.isMac ? e.metaKey : e.ctrlKey;
1104 		}
1105 	};
1106 })(tinymce);
1107 
1108 tinymce.util.Quirks = function(editor) {
1109 	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
1110 
1111 	function setEditorCommandState(cmd, state) {
1112 		try {
1113 			editor.getDoc().execCommand(cmd, false, state);
1114 		} catch (ex) {
1115 			// Ignore
1116 		}
1117 	}
1118 
1119 	function getDocumentMode() {
1120 		var documentMode = editor.getDoc().documentMode;
1121 
1122 		return documentMode ? documentMode : 6;
1123 	};
1124 
1125 	function cleanupStylesWhenDeleting() {
1126 		function removeMergedFormatSpans(isDelete) {
1127 			var rng, blockElm, node, clonedSpan;
1128 
1129 			rng = selection.getRng();
1130 
1131 			// Find root block
1132 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1133 
1134 			// On delete clone the root span of the next block element
1135 			if (isDelete)
1136 				blockElm = dom.getNext(blockElm, dom.isBlock);
1137 
1138 			// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1139 			if (blockElm) {
1140 				node = blockElm.firstChild;
1141 
1142 				// Ignore empty text nodes
1143 				while (node && node.nodeType == 3 && node.nodeValue.length === 0)
1144 					node = node.nextSibling;
1145 
1146 				if (node && node.nodeName === 'SPAN') {
1147 					clonedSpan = node.cloneNode(false);
1148 				}
1149 			}
1150 
1151 			// Do the backspace/delete action
1152 			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1153 
1154 			// Find all odd apple-style-spans
1155 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1156 			tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1157 				var bm = selection.getBookmark();
1158 
1159 				if (clonedSpan) {
1160 					dom.replace(clonedSpan.cloneNode(false), span, true);
1161 				} else {
1162 					dom.remove(span, true);
1163 				}
1164 
1165 				// Restore the selection
1166 				selection.moveToBookmark(bm);
1167 			});
1168 		};
1169 
1170 		editor.onKeyDown.add(function(editor, e) {
1171 			var isDelete;
1172 
1173 			isDelete = e.keyCode == DELETE;
1174 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1175 				e.preventDefault();
1176 				removeMergedFormatSpans(isDelete);
1177 			}
1178 		});
1179 
1180 		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1181 	};
1182 	
1183 	function emptyEditorWhenDeleting() {
1184 		function serializeRng(rng) {
1185 			var body = dom.create("body");
1186 			var contents = rng.cloneContents();
1187 			body.appendChild(contents);
1188 			return selection.serializer.serialize(body, {format: 'html'});
1189 		}
1190 
1191 		function allContentsSelected(rng) {
1192 			var selection = serializeRng(rng);
1193 
1194 			var allRng = dom.createRng();
1195 			allRng.selectNode(editor.getBody());
1196 
1197 			var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
1198 			return selection === allSelection;
1199 		}
1200 
1201 		editor.onKeyDown.add(function(editor, e) {
1202 			var keyCode = e.keyCode, isCollapsed;
1203 
1204 			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1205 			if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1206 				isCollapsed = editor.selection.isCollapsed();
1207 
1208 				// Selection is collapsed but the editor isn't empty
1209 				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1210 					return;
1211 				}
1212 
1213 				// IE deletes all contents correctly when everything is selected
1214 				if (tinymce.isIE && !isCollapsed) {
1215 					return;
1216 				}
1217 
1218 				// Selection isn't collapsed but not all the contents is selected
1219 				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1220 					return;
1221 				}
1222 
1223 				// Manually empty the editor
1224 				editor.setContent('');
1225 				editor.selection.setCursorLocation(editor.getBody(), 0);
1226 				editor.nodeChanged();
1227 			}
1228 		});
1229 	};
1230 
1231 	function selectAll() {
1232 		editor.onKeyDown.add(function(editor, e) {
1233 			if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
1234 				e.preventDefault();
1235 				editor.execCommand('SelectAll');
1236 			}
1237 		});
1238 	};
1239 
1240 	function inputMethodFocus() {
1241 		if (!editor.settings.content_editable) {
1242 			// Case 1 IME doesn't initialize if you focus the document
1243 			dom.bind(editor.getDoc(), 'focusin', function(e) {
1244 				selection.setRng(selection.getRng());
1245 			});
1246 
1247 			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1248 			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1249 				if (e.target == editor.getDoc().documentElement) {
1250 					editor.getWin().focus();
1251 					selection.setRng(selection.getRng());
1252 				}
1253 			});
1254 		}
1255 	};
1256 
1257 	function removeHrOnBackspace() {
1258 		editor.onKeyDown.add(function(editor, e) {
1259 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1260 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1261 					var node = selection.getNode();
1262 					var previousSibling = node.previousSibling;
1263 
1264 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1265 						dom.remove(previousSibling);
1266 						tinymce.dom.Event.cancel(e);
1267 					}
1268 				}
1269 			}
1270 		})
1271 	}
1272 
1273 	function focusBody() {
1274 		// Fix for a focus bug in FF 3.x where the body element
1275 		// wouldn't get proper focus if the user clicked on the HTML element
1276 		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1277 			editor.onMouseDown.add(function(editor, e) {
1278 				if (e.target.nodeName === "HTML") {
1279 					var body = editor.getBody();
1280 
1281 					// Blur the body it's focused but not correctly focused
1282 					body.blur();
1283 
1284 					// Refocus the body after a little while
1285 					setTimeout(function() {
1286 						body.focus();
1287 					}, 0);
1288 				}
1289 			});
1290 		}
1291 	};
1292 
1293 	function selectControlElements() {
1294 		editor.onClick.add(function(editor, e) {
1295 			e = e.target;
1296 
1297 			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1298 			// WebKit can't even do simple things like selecting an image
1299 			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1300 			if (/^(IMG|HR)$/.test(e.nodeName)) {
1301 				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1302 			}
1303 
1304 			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1305 				selection.select(e);
1306 			}
1307 
1308 			editor.nodeChanged();
1309 		});
1310 	};
1311 
1312 	function removeStylesWhenDeletingAccrossBlockElements() {
1313 		function getAttributeApplyFunction() {
1314 			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1315 
1316 			return function() {
1317 				var target = selection.getStart();
1318 
1319 				if (target !== editor.getBody()) {
1320 					dom.setAttrib(target, "style", null);
1321 
1322 					tinymce.each(template, function(attr) {
1323 						target.setAttributeNode(attr.cloneNode(true));
1324 					});
1325 				}
1326 			};
1327 		}
1328 
1329 		function isSelectionAcrossElements() {
1330 			return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1331 		}
1332 
1333 		function blockEvent(editor, e) {
1334 			e.preventDefault();
1335 			return false;
1336 		}
1337 
1338 		editor.onKeyPress.add(function(editor, e) {
1339 			var applyAttributes;
1340 
1341 			if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1342 				applyAttributes = getAttributeApplyFunction();
1343 				editor.getDoc().execCommand('delete', false, null);
1344 				applyAttributes();
1345 				e.preventDefault();
1346 				return false;
1347 			}
1348 		});
1349 
1350 		dom.bind(editor.getDoc(), 'cut', function(e) {
1351 			var applyAttributes;
1352 
1353 			if (isSelectionAcrossElements()) {
1354 				applyAttributes = getAttributeApplyFunction();
1355 				editor.onKeyUp.addToTop(blockEvent);
1356 
1357 				setTimeout(function() {
1358 					applyAttributes();
1359 					editor.onKeyUp.remove(blockEvent);
1360 				}, 0);
1361 			}
1362 		});
1363 	}
1364 
1365 	function selectionChangeNodeChanged() {
1366 		var lastRng, selectionTimer;
1367 
1368 		dom.bind(editor.getDoc(), 'selectionchange', function() {
1369 			if (selectionTimer) {
1370 				clearTimeout(selectionTimer);
1371 				selectionTimer = 0;
1372 			}
1373 
1374 			selectionTimer = window.setTimeout(function() {
1375 				var rng = selection.getRng();
1376 
1377 				// Compare the ranges to see if it was a real change or not
1378 				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1379 					editor.nodeChanged();
1380 					lastRng = rng;
1381 				}
1382 			}, 50);
1383 		});
1384 	}
1385 
1386 	function ensureBodyHasRoleApplication() {
1387 		document.body.setAttribute("role", "application");
1388 	}
1389 
1390 	function disableBackspaceIntoATable() {
1391 		editor.onKeyDown.add(function(editor, e) {
1392 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1393 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1394 					var previousSibling = selection.getNode().previousSibling;
1395 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1396 						return tinymce.dom.Event.cancel(e);
1397 					}
1398 				}
1399 			}
1400 		})
1401 	}
1402 
1403 	function addNewLinesBeforeBrInPre() {
1404 		// IE8+ rendering mode does the right thing with BR in PRE
1405 		if (getDocumentMode() > 7) {
1406 			return;
1407 		}
1408 
1409 		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1410 		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1411 		setEditorCommandState('RespectVisibilityInDesign', true);
1412 		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1413 		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1414 
1415 		// Adds a \n before all BR elements in PRE to get them visual
1416 		editor.parser.addNodeFilter('pre', function(nodes, name) {
1417 			var i = nodes.length, brNodes, j, brElm, sibling;
1418 
1419 			while (i--) {
1420 				brNodes = nodes[i].getAll('br');
1421 				j = brNodes.length;
1422 				while (j--) {
1423 					brElm = brNodes[j];
1424 
1425 					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1426 					sibling = brElm.prev;
1427 					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1428 						sibling.value += '\n';
1429 					} else {
1430 						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1431 					}
1432 				}
1433 			}
1434 		});
1435 
1436 		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1437 		editor.serializer.addNodeFilter('pre', function(nodes, name) {
1438 			var i = nodes.length, brNodes, j, brElm, sibling;
1439 
1440 			while (i--) {
1441 				brNodes = nodes[i].getAll('br');
1442 				j = brNodes.length;
1443 				while (j--) {
1444 					brElm = brNodes[j];
1445 					sibling = brElm.prev;
1446 					if (sibling && sibling.type == 3) {
1447 						sibling.value = sibling.value.replace(/\r?\n$/, '');
1448 					}
1449 				}
1450 			}
1451 		});
1452 	}
1453 
1454 	function removePreSerializedStylesWhenSelectingControls() {
1455 		dom.bind(editor.getBody(), 'mouseup', function(e) {
1456 			var value, node = selection.getNode();
1457 
1458 			// Moved styles to attributes on IMG eements
1459 			if (node.nodeName == 'IMG') {
1460 				// Convert style width to width attribute
1461 				if (value = dom.getStyle(node, 'width')) {
1462 					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1463 					dom.setStyle(node, 'width', '');
1464 				}
1465 
1466 				// Convert style height to height attribute
1467 				if (value = dom.getStyle(node, 'height')) {
1468 					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1469 					dom.setStyle(node, 'height', '');
1470 				}
1471 			}
1472 		});
1473 	}
1474 
1475 	function keepInlineElementOnDeleteBackspace() {
1476 		editor.onKeyDown.add(function(editor, e) {
1477 			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1478 
1479 			isDelete = e.keyCode == DELETE;
1480 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1481 				rng = selection.getRng();
1482 				container = rng.startContainer;
1483 				offset = rng.startOffset;
1484 				collapsed = rng.collapsed;
1485 
1486 				// Override delete if the start container is a text node and is at the beginning of text or
1487 				// just before/after the last character to be deleted in collapsed mode
1488 				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1489 					nonEmptyElements = editor.schema.getNonEmptyElements();
1490 
1491 					// Prevent default logic since it's broken
1492 					e.preventDefault();
1493 
1494 					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1495 					brElm = dom.create('br', {id: '__tmp'});
1496 					container.parentNode.insertBefore(brElm, container);
1497 
1498 					// Do the browser delete
1499 					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1500 
1501 					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1502 					container = selection.getRng().startContainer;
1503 					sibling = container.previousSibling;
1504 					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1505 						dom.remove(sibling);
1506 					}
1507 
1508 					// Remove the temp element we inserted
1509 					dom.remove('__tmp');
1510 				}
1511 			}
1512 		});
1513 	}
1514 
1515 	function removeBlockQuoteOnBackSpace() {
1516 		// Add block quote deletion handler
1517 		editor.onKeyDown.add(function(editor, e) {
1518 			var rng, container, offset, root, parent;
1519 
1520 			if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1521 				return;
1522 			}
1523 
1524 			rng = selection.getRng();
1525 			container = rng.startContainer;
1526 			offset = rng.startOffset;
1527 			root = dom.getRoot();
1528 			parent = container;
1529 
1530 			if (!rng.collapsed || offset !== 0) {
1531 				return;
1532 			}
1533 
1534 			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1535 				parent = parent.parentNode;
1536 			}
1537 
1538 			// Is the cursor at the beginning of a blockquote?
1539 			if (parent.tagName === 'BLOCKQUOTE') {
1540 				// Remove the blockquote
1541 				editor.formatter.toggle('blockquote', null, parent);
1542 
1543 				// Move the caret to the beginning of container
1544 				rng.setStart(container, 0);
1545 				rng.setEnd(container, 0);
1546 				selection.setRng(rng);
1547 				selection.collapse(false);
1548 			}
1549 		});
1550 	};
1551 
1552 	function setGeckoEditingOptions() {
1553 		function setOpts() {
1554 			editor._refreshContentEditable();
1555 
1556 			setEditorCommandState("StyleWithCSS", false);
1557 			setEditorCommandState("enableInlineTableEditing", false);
1558 
1559 			if (!settings.object_resizing) {
1560 				setEditorCommandState("enableObjectResizing", false);
1561 			}
1562 		};
1563 
1564 		if (!settings.readonly) {
1565 			editor.onBeforeExecCommand.add(setOpts);
1566 			editor.onMouseDown.add(setOpts);
1567 		}
1568 	};
1569 
1570 	function addBrAfterLastLinks() {
1571 		function fixLinks(editor, o) {
1572 			tinymce.each(dom.select('a'), function(node) {
1573 				var parentNode = node.parentNode, root = dom.getRoot();
1574 
1575 				if (parentNode.lastChild === node) {
1576 					while (parentNode && !dom.isBlock(parentNode)) {
1577 						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1578 							return;
1579 						}
1580 
1581 						parentNode = parentNode.parentNode;
1582 					}
1583 
1584 					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1585 				}
1586 			});
1587 		};
1588 
1589 		editor.onExecCommand.add(function(editor, cmd) {
1590 			if (cmd === 'CreateLink') {
1591 				fixLinks(editor);
1592 			}
1593 		});
1594 
1595 		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1596 	};
1597 
1598 	function setDefaultBlockType() {
1599 		if (settings.forced_root_block) {
1600 			editor.onInit.add(function() {
1601 				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1602 			});
1603 		}
1604 	}
1605 
1606 	function removeGhostSelection() {
1607 		function repaint(sender, args) {
1608 			if (!sender || !args.initial) {
1609 				editor.execCommand('mceRepaint');
1610 			}
1611 		};
1612 
1613 		editor.onUndo.add(repaint);
1614 		editor.onRedo.add(repaint);
1615 		editor.onSetContent.add(repaint);
1616 	};
1617 
1618 	function deleteControlItemOnBackSpace() {
1619 		editor.onKeyDown.add(function(editor, e) {
1620 			var rng;
1621 
1622 			if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
1623 				rng = editor.getDoc().selection.createRange();
1624 				if (rng && rng.item) {
1625 					e.preventDefault();
1626 					editor.undoManager.beforeChange();
1627 					dom.remove(rng.item(0));
1628 					editor.undoManager.add();
1629 				}
1630 			}
1631 		});
1632 	};
1633 
1634 	function renderEmptyBlocksFix() {
1635 		var emptyBlocksCSS;
1636 
1637 		// IE10+
1638 		if (getDocumentMode() >= 10) {
1639 			emptyBlocksCSS = '';
1640 			tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1641 				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1642 			});
1643 
1644 			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1645 		}
1646 	};
1647 
1648 	function fakeImageResize() {
1649 		var mouseDownImg, startX, startY, startW, startH;
1650 
1651 		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1652 			return;
1653 		}
1654 
1655 		editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
1656 
1657 		function resizeImage(e) {
1658 			var deltaX, deltaY, ratio, width, height;
1659 
1660 			if (mouseDownImg) {
1661 				deltaX = e.screenX - startX;
1662 				deltaY = e.screenY - startY;
1663 				ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
1664 
1665 				// Only update styles if the user draged one pixel or more
1666 				if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
1667 					// Constrain proportions
1668 					width = Math.round(startW * ratio);
1669 					height = Math.round(startH * ratio);
1670 
1671 					// Resize by using style or attribute
1672 					if (mouseDownImg.style.width) {
1673 						dom.setStyle(mouseDownImg, 'width', width);
1674 					} else {
1675 						dom.setAttrib(mouseDownImg, 'width', width);
1676 					}
1677 
1678 					// Resize by using style or attribute
1679 					if (mouseDownImg.style.height) {
1680 						dom.setStyle(mouseDownImg, 'height', height);
1681 					} else {
1682 						dom.setAttrib(mouseDownImg, 'height', height);
1683 					}
1684 
1685 					if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
1686 						dom.addClass(editor.getBody(), 'mceResizeImages');
1687 					}
1688 				}
1689 			}
1690 		};
1691 
1692 		editor.onMouseDown.add(function(editor, e) {
1693 			var target = e.target;
1694 
1695 			if (target.nodeName == "IMG") {
1696 				mouseDownImg = target;
1697 				startX = e.screenX;
1698 				startY = e.screenY;
1699 				startW = mouseDownImg.clientWidth;
1700 				startH = mouseDownImg.clientHeight;
1701 				dom.bind(editor.getDoc(), 'mousemove', resizeImage);
1702 				e.preventDefault();
1703 			}
1704 		});
1705 
1706 		// Unbind events on node change and restore resize cursor
1707 		editor.onNodeChange.add(function() {
1708 			if (mouseDownImg) {
1709 				mouseDownImg = null;
1710 				dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
1711 			}
1712 
1713 			if (selection.getNode().nodeName == "IMG") {
1714 				dom.addClass(editor.getBody(), 'mceResizeImages');
1715 			} else {
1716 				dom.removeClass(editor.getBody(), 'mceResizeImages');
1717 			}
1718 		});
1719 	};
1720 
1721 	// All browsers
1722 	disableBackspaceIntoATable();
1723 	removeBlockQuoteOnBackSpace();
1724 	emptyEditorWhenDeleting();
1725 
1726 	// WebKit
1727 	if (tinymce.isWebKit) {
1728 		keepInlineElementOnDeleteBackspace();
1729 		cleanupStylesWhenDeleting();
1730 		inputMethodFocus();
1731 		selectControlElements();
1732 		setDefaultBlockType();
1733 
1734 		// iOS
1735 		if (tinymce.isIDevice) {
1736 			selectionChangeNodeChanged();
1737 		} else {
1738 			fakeImageResize();
1739 			selectAll();
1740 		}
1741 	}
1742 
1743 	// IE
1744 	if (tinymce.isIE) {
1745 		removeHrOnBackspace();
1746 		ensureBodyHasRoleApplication();
1747 		addNewLinesBeforeBrInPre();
1748 		removePreSerializedStylesWhenSelectingControls();
1749 		deleteControlItemOnBackSpace();
1750 		renderEmptyBlocksFix();
1751 	}
1752 
1753 	// Gecko
1754 	if (tinymce.isGecko) {
1755 		removeHrOnBackspace();
1756 		focusBody();
1757 		removeStylesWhenDeletingAccrossBlockElements();
1758 		setGeckoEditingOptions();
1759 		addBrAfterLastLinks();
1760 		removeGhostSelection();
1761 	}
1762 };
1763 (function(tinymce) {
1764 	var namedEntities, baseEntities, reverseEntities,
1765 		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1766 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1767 		rawCharsRegExp = /[<>&\"\']/g,
1768 		entityRegExp = /&(#x|#)?([\w]+);/g,
1769 		asciiMap = {
1770 				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1771 				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1772 				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1773 				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1774 				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1775 		};
1776 
1777 	// Raw entities
1778 	baseEntities = {
1779 		'\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
1780 		"'" : ''',
1781 		'<' : '<',
1782 		'>' : '>',
1783 		'&' : '&'
1784 	};
1785 
1786 	// Reverse lookup table for raw entities
1787 	reverseEntities = {
1788 		'<' : '<',
1789 		'>' : '>',
1790 		'&' : '&',
1791 		'"' : '"',
1792 		''' : "'"
1793 	};
1794 
1795 	// Decodes text by using the browser
1796 	function nativeDecode(text) {
1797 		var elm;
1798 
1799 		elm = document.createElement("div");
1800 		elm.innerHTML = text;
1801 
1802 		return elm.textContent || elm.innerText || text;
1803 	};
1804 
1805 	// Build a two way lookup table for the entities
1806 	function buildEntitiesLookup(items, radix) {
1807 		var i, chr, entity, lookup = {};
1808 
1809 		if (items) {
1810 			items = items.split(',');
1811 			radix = radix || 10;
1812 
1813 			// Build entities lookup table
1814 			for (i = 0; i < items.length; i += 2) {
1815 				chr = String.fromCharCode(parseInt(items[i], radix));
1816 
1817 				// Only add non base entities
1818 				if (!baseEntities[chr]) {
1819 					entity = '&' + items[i + 1] + ';';
1820 					lookup[chr] = entity;
1821 					lookup[entity] = chr;
1822 				}
1823 			}
1824 
1825 			return lookup;
1826 		}
1827 	};
1828 
1829 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
1830 	namedEntities = buildEntitiesLookup(
1831 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1832 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1833 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1834 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1835 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1836 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1837 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1838 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1839 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1840 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1841 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1842 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1843 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1844 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1845 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1846 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1847 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1848 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1849 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1850 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1851 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1852 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1853 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1854 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1855 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
1856 
1857 	tinymce.html = tinymce.html || {};
1858 
1859 	tinymce.html.Entities = {
1860 		encodeRaw : function(text, attr) {
1861 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1862 				return baseEntities[chr] || chr;
1863 			});
1864 		},
1865 
1866 		encodeAllRaw : function(text) {
1867 			return ('' + text).replace(rawCharsRegExp, function(chr) {
1868 				return baseEntities[chr] || chr;
1869 			});
1870 		},
1871 
1872 		encodeNumeric : function(text, attr) {
1873 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1874 				// Multi byte sequence convert it to a single entity
1875 				if (chr.length > 1)
1876 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1877 
1878 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1879 			});
1880 		},
1881 
1882 		encodeNamed : function(text, attr, entities) {
1883 			entities = entities || namedEntities;
1884 
1885 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1886 				return baseEntities[chr] || entities[chr] || chr;
1887 			});
1888 		},
1889 
1890 		getEncodeFunc : function(name, entities) {
1891 			var Entities = tinymce.html.Entities;
1892 
1893 			entities = buildEntitiesLookup(entities) || namedEntities;
1894 
1895 			function encodeNamedAndNumeric(text, attr) {
1896 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1897 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1898 				});
1899 			};
1900 
1901 			function encodeCustomNamed(text, attr) {
1902 				return Entities.encodeNamed(text, attr, entities);
1903 			};
1904 
1905 			// Replace + with , to be compatible with previous TinyMCE versions
1906 			name = tinymce.makeMap(name.replace(/\+/g, ','));
1907 
1908 			// Named and numeric encoder
1909 			if (name.named && name.numeric)
1910 				return encodeNamedAndNumeric;
1911 
1912 			// Named encoder
1913 			if (name.named) {
1914 				// Custom names
1915 				if (entities)
1916 					return encodeCustomNamed;
1917 
1918 				return Entities.encodeNamed;
1919 			}
1920 
1921 			// Numeric
1922 			if (name.numeric)
1923 				return Entities.encodeNumeric;
1924 
1925 			// Raw encoder
1926 			return Entities.encodeRaw;
1927 		},
1928 
1929 		decode : function(text) {
1930 			return text.replace(entityRegExp, function(all, numeric, value) {
1931 				if (numeric) {
1932 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
1933 
1934 					// Support upper UTF
1935 					if (value > 0xFFFF) {
1936 						value -= 0x10000;
1937 
1938 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1939 					} else
1940 						return asciiMap[value] || String.fromCharCode(value);
1941 				}
1942 
1943 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1944 			});
1945 		}
1946 	};
1947 })(tinymce);
1948 
1949 tinymce.html.Styles = function(settings, schema) {
1950 	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1951 		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1952 		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1953 		trimRightRegExp = /\s+$/,
1954 		urlColorRegExp = /rgb/,
1955 		undef, i, encodingLookup = {}, encodingItems;
1956 
1957 	settings = settings || {};
1958 
1959 	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1960 	for (i = 0; i < encodingItems.length; i++) {
1961 		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1962 		encodingLookup['\uFEFF' + i] = encodingItems[i];
1963 	}
1964 
1965 	function toHex(match, r, g, b) {
1966 		function hex(val) {
1967 			val = parseInt(val).toString(16);
1968 
1969 			return val.length > 1 ? val : '0' + val; // 0 -> 00
1970 		};
1971 
1972 		return '#' + hex(r) + hex(g) + hex(b);
1973 	};
1974 
1975 	return {
1976 		toHex : function(color) {
1977 			return color.replace(rgbRegExp, toHex);
1978 		},
1979 
1980 		parse : function(css) {
1981 			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1982 
1983 			function compress(prefix, suffix) {
1984 				var top, right, bottom, left;
1985 
1986 				// Get values and check it it needs compressing
1987 				top = styles[prefix + '-top' + suffix];
1988 				if (!top)
1989 					return;
1990 
1991 				right = styles[prefix + '-right' + suffix];
1992 				if (top != right)
1993 					return;
1994 
1995 				bottom = styles[prefix + '-bottom' + suffix];
1996 				if (right != bottom)
1997 					return;
1998 
1999 				left = styles[prefix + '-left' + suffix];
2000 				if (bottom != left)
2001 					return;
2002 
2003 				// Compress
2004 				styles[prefix + suffix] = left;
2005 				delete styles[prefix + '-top' + suffix];
2006 				delete styles[prefix + '-right' + suffix];
2007 				delete styles[prefix + '-bottom' + suffix];
2008 				delete styles[prefix + '-left' + suffix];
2009 			};
2010 
2011 			function canCompress(key) {
2012 				var value = styles[key], i;
2013 
2014 				if (!value || value.indexOf(' ') < 0)
2015 					return;
2016 
2017 				value = value.split(' ');
2018 				i = value.length;
2019 				while (i--) {
2020 					if (value[i] !== value[0])
2021 						return false;
2022 				}
2023 
2024 				styles[key] = value[0];
2025 
2026 				return true;
2027 			};
2028 
2029 			function compress2(target, a, b, c) {
2030 				if (!canCompress(a))
2031 					return;
2032 
2033 				if (!canCompress(b))
2034 					return;
2035 
2036 				if (!canCompress(c))
2037 					return;
2038 
2039 				// Compress
2040 				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2041 				delete styles[a];
2042 				delete styles[b];
2043 				delete styles[c];
2044 			};
2045 
2046 			// Encodes the specified string by replacing all \" \' ; : with _<num>
2047 			function encode(str) {
2048 				isEncoded = true;
2049 
2050 				return encodingLookup[str];
2051 			};
2052 
2053 			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2054 			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2055 			function decode(str, keep_slashes) {
2056 				if (isEncoded) {
2057 					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2058 						return encodingLookup[str];
2059 					});
2060 				}
2061 
2062 				if (!keep_slashes)
2063 					str = str.replace(/\\([\'\";:])/g, "$1");
2064 
2065 				return str;
2066 			};
2067 
2068 			function processUrl(match, url, url2, url3, str, str2) {
2069 				str = str || str2;
2070 
2071 				if (str) {
2072 					str = decode(str);
2073 
2074 					// Force strings into single quote format
2075 					return "'" + str.replace(/\'/g, "\\'") + "'";
2076 				}
2077 
2078 				url = decode(url || url2 || url3);
2079 
2080 				// Convert the URL to relative/absolute depending on config
2081 				if (urlConverter)
2082 					url = urlConverter.call(urlConverterScope, url, 'style');
2083 
2084 				// Output new URL format
2085 				return "url('" + url.replace(/\'/g, "\\'") + "')";
2086 			};
2087 
2088 			if (css) {
2089 				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2090 				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2091 					return str.replace(/[;:]/g, encode);
2092 				});
2093 
2094 				// Parse styles
2095 				while (matches = styleRegExp.exec(css)) {
2096 					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2097 					value = matches[2].replace(trimRightRegExp, '');
2098 
2099 					if (name && value.length > 0) {
2100 						// Opera will produce 700 instead of bold in their style values
2101 						if (name === 'font-weight' && value === '700')
2102 							value = 'bold';
2103 						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2104 							value = value.toLowerCase();		
2105 
2106 						// Convert RGB colors to HEX
2107 						value = value.replace(rgbRegExp, toHex);
2108 
2109 						// Convert URLs and force them into url('value') format
2110 						value = value.replace(urlOrStrRegExp, processUrl);
2111 						styles[name] = isEncoded ? decode(value, true) : value;
2112 					}
2113 
2114 					styleRegExp.lastIndex = matches.index + matches[0].length;
2115 				}
2116 
2117 				// Compress the styles to reduce it's size for example IE will expand styles
2118 				compress("border", "");
2119 				compress("border", "-width");
2120 				compress("border", "-color");
2121 				compress("border", "-style");
2122 				compress("padding", "");
2123 				compress("margin", "");
2124 				compress2('border', 'border-width', 'border-style', 'border-color');
2125 
2126 				// Remove pointless border, IE produces these
2127 				if (styles.border === 'medium none')
2128 					delete styles.border;
2129 			}
2130 
2131 			return styles;
2132 		},
2133 
2134 		serialize : function(styles, element_name) {
2135 			var css = '', name, value;
2136 
2137 			function serializeStyles(name) {
2138 				var styleList, i, l, value;
2139 
2140 				styleList = schema.styles[name];
2141 				if (styleList) {
2142 					for (i = 0, l = styleList.length; i < l; i++) {
2143 						name = styleList[i];
2144 						value = styles[name];
2145 
2146 						if (value !== undef && value.length > 0)
2147 							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2148 					}
2149 				}
2150 			};
2151 
2152 			// Serialize styles according to schema
2153 			if (element_name && schema && schema.styles) {
2154 				// Serialize global styles and element specific styles
2155 				serializeStyles('*');
2156 				serializeStyles(element_name);
2157 			} else {
2158 				// Output the styles in the order they are inside the object
2159 				for (name in styles) {
2160 					value = styles[name];
2161 
2162 					if (value !== undef && value.length > 0)
2163 						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2164 				}
2165 			}
2166 
2167 			return css;
2168 		}
2169 	};
2170 };
2171 
2172 (function(tinymce) {
2173 	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2174 
2175 	function split(str, delim) {
2176 		return str.split(delim || ',');
2177 	};
2178 
2179 	function unpack(lookup, data) {
2180 		var key, elements = {};
2181 
2182 		function replace(value) {
2183 			return value.replace(/[A-Z]+/g, function(key) {
2184 				return replace(lookup[key]);
2185 			});
2186 		};
2187 
2188 		// Unpack lookup
2189 		for (key in lookup) {
2190 			if (lookup.hasOwnProperty(key))
2191 				lookup[key] = replace(lookup[key]);
2192 		}
2193 
2194 		// Unpack and parse data into object map
2195 		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2196 			attributes = split(attributes, '|');
2197 
2198 			elements[name] = {
2199 				attributes : makeMap(attributes),
2200 				attributesOrder : attributes,
2201 				children : makeMap(children, '|', {'#comment' : {}})
2202 			}
2203 		});
2204 
2205 		return elements;
2206 	};
2207 
2208 	function getHTML5() {
2209 		var html5 = mapCache.html5;
2210 
2211 		if (!html5) {
2212 			html5 = mapCache.html5 = unpack({
2213 					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2214 					B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2215 						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2216 					C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2217 						'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2218 						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2219 				}, 'html[A|manifest][body|head]' +
2220 					'head[A][base|command|link|meta|noscript|script|style|title]' +
2221 					'title[A][#]' +
2222 					'base[A|href|target][]' +
2223 					'link[A|href|rel|media|type|sizes][]' +
2224 					'meta[A|http-equiv|name|content|charset][]' +
2225 					'style[A|type|media|scoped][#]' +
2226 					'script[A|charset|type|src|defer|async][#]' +
2227 					'noscript[A][C]' +
2228 					'body[A][C]' +
2229 					'section[A][C]' +
2230 					'nav[A][C]' +
2231 					'article[A][C]' +
2232 					'aside[A][C]' +
2233 					'h1[A][B]' +
2234 					'h2[A][B]' +
2235 					'h3[A][B]' +
2236 					'h4[A][B]' +
2237 					'h5[A][B]' +
2238 					'h6[A][B]' +
2239 					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2240 					'header[A][C]' +
2241 					'footer[A][C]' +
2242 					'address[A][C]' +
2243 					'p[A][B]' +
2244 					'br[A][]' +
2245 					'pre[A][B]' +
2246 					'dialog[A][dd|dt]' +
2247 					'blockquote[A|cite][C]' +
2248 					'ol[A|start|reversed][li]' +
2249 					'ul[A][li]' +
2250 					'li[A|value][C]' +
2251 					'dl[A][dd|dt]' +
2252 					'dt[A][B]' +
2253 					'dd[A][C]' +
2254 					'a[A|href|target|ping|rel|media|type][B]' +
2255 					'em[A][B]' +
2256 					'strong[A][B]' +
2257 					'small[A][B]' +
2258 					'cite[A][B]' +
2259 					'q[A|cite][B]' +
2260 					'dfn[A][B]' +
2261 					'abbr[A][B]' +
2262 					'code[A][B]' +
2263 					'var[A][B]' +
2264 					'samp[A][B]' +
2265 					'kbd[A][B]' +
2266 					'sub[A][B]' +
2267 					'sup[A][B]' +
2268 					'i[A][B]' +
2269 					'b[A][B]' +
2270 					'mark[A][B]' +
2271 					'progress[A|value|max][B]' +
2272 					'meter[A|value|min|max|low|high|optimum][B]' +
2273 					'time[A|datetime][B]' +
2274 					'ruby[A][B|rt|rp]' +
2275 					'rt[A][B]' +
2276 					'rp[A][B]' +
2277 					'bdo[A][B]' +
2278 					'span[A][B]' +
2279 					'ins[A|cite|datetime][B]' +
2280 					'del[A|cite|datetime][B]' +
2281 					'figure[A][C|legend|figcaption]' +
2282 					'figcaption[A][C]' +
2283 					'img[A|alt|src|height|width|usemap|ismap][]' +
2284 					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2285 					'embed[A|src|height|width|type][]' +
2286 					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2287 					'param[A|name|value][]' +
2288 					'details[A|open][C|legend]' +
2289 					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2290 					'menu[A|type|label][C|li]' +
2291 					'legend[A][C|B]' +
2292 					'div[A][C]' +
2293 					'source[A|src|type|media][]' +
2294 					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2295 					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2296 					'hr[A][]' +
2297 					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2298 					'fieldset[A|disabled|form|name][C|legend]' +
2299 					'label[A|form|for][B]' +
2300 					'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2301 						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2302 					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2303 					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2304 					'datalist[A][B|option]' +
2305 					'optgroup[A|disabled|label][option]' +
2306 					'option[A|disabled|selected|label|value][]' +
2307 					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2308 					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2309 					'output[A|for|form|name][B]' +
2310 					'canvas[A|width|height][]' +
2311 					'map[A|name][B|C]' +
2312 					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2313 					'mathml[A][]' +
2314 					'svg[A][]' +
2315 					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2316 					'caption[A][C]' +
2317 					'colgroup[A|span][col]' +
2318 					'col[A|span][]' +
2319 					'thead[A][tr]' +
2320 					'tfoot[A][tr]' +
2321 					'tbody[A][tr]' +
2322 					'tr[A][th|td]' +
2323 					'th[A|headers|rowspan|colspan|scope][B]' +
2324 					'td[A|headers|rowspan|colspan][C]' +
2325 					'wbr[A][]'
2326 			);
2327 		}
2328 
2329 		return html5;
2330 	};
2331 
2332 	function getHTML4() {
2333 		var html4 = mapCache.html4;
2334 
2335 		if (!html4) {
2336 			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2337 			html4 = mapCache.html4 = unpack({
2338 				Z : 'H|K|N|O|P',
2339 				Y : 'X|form|R|Q',
2340 				ZG : 'E|span|width|align|char|charoff|valign',
2341 				X : 'p|T|div|U|W|isindex|fieldset|table',
2342 				ZF : 'E|align|char|charoff|valign',
2343 				W : 'pre|hr|blockquote|address|center|noframes',
2344 				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2345 				ZD : '[E][S]',
2346 				U : 'ul|ol|dl|menu|dir',
2347 				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2348 				T : 'h1|h2|h3|h4|h5|h6',
2349 				ZB : 'X|S|Q',
2350 				S : 'R|P',
2351 				ZA : 'a|G|J|M|O|P',
2352 				R : 'a|H|K|N|O',
2353 				Q : 'noscript|P',
2354 				P : 'ins|del|script',
2355 				O : 'input|select|textarea|label|button',
2356 				N : 'M|L',
2357 				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2358 				L : 'sub|sup',
2359 				K : 'J|I',
2360 				J : 'tt|i|b|u|s|strike',
2361 				I : 'big|small|font|basefont',
2362 				H : 'G|F',
2363 				G : 'br|span|bdo',
2364 				F : 'object|applet|img|map|iframe',
2365 				E : 'A|B|C',
2366 				D : 'accesskey|tabindex|onfocus|onblur',
2367 				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2368 				B : 'lang|xml:lang|dir',
2369 				A : 'id|class|style|title'
2370 			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2371 				'style[B|id|type|media|title|xml:space][]' + 
2372 				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2373 				'param[id|name|value|valuetype|type][]' + 
2374 				'p[E|align][#|S]' + 
2375 				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2376 				'br[A|clear][]' + 
2377 				'span[E][#|S]' + 
2378 				'bdo[A|C|B][#|S]' + 
2379 				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2380 				'h1[E|align][#|S]' + 
2381 				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2382 				'map[B|C|A|name][X|form|Q|area]' + 
2383 				'h2[E|align][#|S]' + 
2384 				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2385 				'h3[E|align][#|S]' + 
2386 				'tt[E][#|S]' + 
2387 				'i[E][#|S]' + 
2388 				'b[E][#|S]' + 
2389 				'u[E][#|S]' + 
2390 				's[E][#|S]' + 
2391 				'strike[E][#|S]' + 
2392 				'big[E][#|S]' + 
2393 				'small[E][#|S]' + 
2394 				'font[A|B|size|color|face][#|S]' + 
2395 				'basefont[id|size|color|face][]' + 
2396 				'em[E][#|S]' + 
2397 				'strong[E][#|S]' + 
2398 				'dfn[E][#|S]' + 
2399 				'code[E][#|S]' + 
2400 				'q[E|cite][#|S]' + 
2401 				'samp[E][#|S]' + 
2402 				'kbd[E][#|S]' + 
2403 				'var[E][#|S]' + 
2404 				'cite[E][#|S]' + 
2405 				'abbr[E][#|S]' + 
2406 				'acronym[E][#|S]' + 
2407 				'sub[E][#|S]' + 
2408 				'sup[E][#|S]' + 
2409 				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2410 				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2411 				'optgroup[E|disabled|label][option]' + 
2412 				'option[E|selected|disabled|label|value][]' + 
2413 				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2414 				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2415 				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2416 				'h4[E|align][#|S]' + 
2417 				'ins[E|cite|datetime][#|Y]' + 
2418 				'h5[E|align][#|S]' + 
2419 				'del[E|cite|datetime][#|Y]' + 
2420 				'h6[E|align][#|S]' + 
2421 				'div[E|align][#|Y]' + 
2422 				'ul[E|type|compact][li]' + 
2423 				'li[E|type|value][#|Y]' + 
2424 				'ol[E|type|compact|start][li]' + 
2425 				'dl[E|compact][dt|dd]' + 
2426 				'dt[E][#|S]' + 
2427 				'dd[E][#|Y]' + 
2428 				'menu[E|compact][li]' + 
2429 				'dir[E|compact][li]' + 
2430 				'pre[E|width|xml:space][#|ZA]' + 
2431 				'hr[E|align|noshade|size|width][]' + 
2432 				'blockquote[E|cite][#|Y]' + 
2433 				'address[E][#|S|p]' + 
2434 				'center[E][#|Y]' + 
2435 				'noframes[E][#|Y]' + 
2436 				'isindex[A|B|prompt][]' + 
2437 				'fieldset[E][#|legend|Y]' + 
2438 				'legend[E|accesskey|align][#|S]' + 
2439 				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2440 				'caption[E|align][#|S]' + 
2441 				'col[ZG][]' + 
2442 				'colgroup[ZG][col]' + 
2443 				'thead[ZF][tr]' + 
2444 				'tr[ZF|bgcolor][th|td]' + 
2445 				'th[E|ZE][#|Y]' + 
2446 				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2447 				'noscript[E][#|Y]' + 
2448 				'td[E|ZE][#|Y]' + 
2449 				'tfoot[ZF][tr]' + 
2450 				'tbody[ZF][tr]' + 
2451 				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2452 				'base[id|href|target][]' + 
2453 				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2454 			);
2455 		}
2456 
2457 		return html4;
2458 	};
2459 
2460 	tinymce.html.Schema = function(settings) {
2461 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2462 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2463 
2464 		// Creates an lookup table map object for the specified option or the default value
2465 		function createLookupTable(option, default_value, extend) {
2466 			var value = settings[option];
2467 
2468 			if (!value) {
2469 				// Get cached default map or make it if needed
2470 				value = mapCache[option];
2471 
2472 				if (!value) {
2473 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2474 					value = tinymce.extend(value, extend);
2475 
2476 					mapCache[option] = value;
2477 				}
2478 			} else {
2479 				// Create custom map
2480 				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2481 			}
2482 
2483 			return value;
2484 		};
2485 
2486 		settings = settings || {};
2487 		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2488 
2489 		// Allow all elements and attributes if verify_html is set to false
2490 		if (settings.verify_html === false)
2491 			settings.valid_elements = '*[*]';
2492 
2493 		// Build styles list
2494 		if (settings.valid_styles) {
2495 			validStyles = {};
2496 
2497 			// Convert styles into a rule list
2498 			each(settings.valid_styles, function(value, key) {
2499 				validStyles[key] = tinymce.explode(value);
2500 			});
2501 		}
2502 
2503 		// Setup map objects
2504 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2505 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2506 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2507 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2508 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2509 		blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 
2510 						'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 
2511 						'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
2512 
2513 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2514 		function patternToRegExp(str) {
2515 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2516 		};
2517 
2518 		// Parses the specified valid_elements string and adds to the current rules
2519 		// This function is a bit hard to read since it's heavily optimized for speed
2520 		function addValidElements(valid_elements) {
2521 			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2522 				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2523 				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2524 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2525 				hasPatternsRegExp = /[*?+]/;
2526 
2527 			if (valid_elements) {
2528 				// Split valid elements into an array with rules
2529 				valid_elements = split(valid_elements);
2530 
2531 				if (elements['@']) {
2532 					globalAttributes = elements['@'].attributes;
2533 					globalAttributesOrder = elements['@'].attributesOrder;
2534 				}
2535 
2536 				// Loop all rules
2537 				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2538 					// Parse element rule
2539 					matches = elementRuleRegExp.exec(valid_elements[ei]);
2540 					if (matches) {
2541 						// Setup local names for matches
2542 						prefix = matches[1];
2543 						elementName = matches[2];
2544 						outputName = matches[3];
2545 						attrData = matches[4];
2546 
2547 						// Create new attributes and attributesOrder
2548 						attributes = {};
2549 						attributesOrder = [];
2550 
2551 						// Create the new element
2552 						element = {
2553 							attributes : attributes,
2554 							attributesOrder : attributesOrder
2555 						};
2556 
2557 						// Padd empty elements prefix
2558 						if (prefix === '#')
2559 							element.paddEmpty = true;
2560 
2561 						// Remove empty elements prefix
2562 						if (prefix === '-')
2563 							element.removeEmpty = true;
2564 
2565 						// Copy attributes from global rule into current rule
2566 						if (globalAttributes) {
2567 							for (key in globalAttributes)
2568 								attributes[key] = globalAttributes[key];
2569 
2570 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2571 						}
2572 
2573 						// Attributes defined
2574 						if (attrData) {
2575 							attrData = split(attrData, '|');
2576 							for (ai = 0, al = attrData.length; ai < al; ai++) {
2577 								matches = attrRuleRegExp.exec(attrData[ai]);
2578 								if (matches) {
2579 									attr = {};
2580 									attrType = matches[1];
2581 									attrName = matches[2].replace(/::/g, ':');
2582 									prefix = matches[3];
2583 									value = matches[4];
2584 
2585 									// Required
2586 									if (attrType === '!') {
2587 										element.attributesRequired = element.attributesRequired || [];
2588 										element.attributesRequired.push(attrName);
2589 										attr.required = true;
2590 									}
2591 
2592 									// Denied from global
2593 									if (attrType === '-') {
2594 										delete attributes[attrName];
2595 										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2596 										continue;
2597 									}
2598 
2599 									// Default value
2600 									if (prefix) {
2601 										// Default value
2602 										if (prefix === '=') {
2603 											element.attributesDefault = element.attributesDefault || [];
2604 											element.attributesDefault.push({name: attrName, value: value});
2605 											attr.defaultValue = value;
2606 										}
2607 
2608 										// Forced value
2609 										if (prefix === ':') {
2610 											element.attributesForced = element.attributesForced || [];
2611 											element.attributesForced.push({name: attrName, value: value});
2612 											attr.forcedValue = value;
2613 										}
2614 
2615 										// Required values
2616 										if (prefix === '<')
2617 											attr.validValues = makeMap(value, '?');
2618 									}
2619 
2620 									// Check for attribute patterns
2621 									if (hasPatternsRegExp.test(attrName)) {
2622 										element.attributePatterns = element.attributePatterns || [];
2623 										attr.pattern = patternToRegExp(attrName);
2624 										element.attributePatterns.push(attr);
2625 									} else {
2626 										// Add attribute to order list if it doesn't already exist
2627 										if (!attributes[attrName])
2628 											attributesOrder.push(attrName);
2629 
2630 										attributes[attrName] = attr;
2631 									}
2632 								}
2633 							}
2634 						}
2635 
2636 						// Global rule, store away these for later usage
2637 						if (!globalAttributes && elementName == '@') {
2638 							globalAttributes = attributes;
2639 							globalAttributesOrder = attributesOrder;
2640 						}
2641 
2642 						// Handle substitute elements such as b/strong
2643 						if (outputName) {
2644 							element.outputName = elementName;
2645 							elements[outputName] = element;
2646 						}
2647 
2648 						// Add pattern or exact element
2649 						if (hasPatternsRegExp.test(elementName)) {
2650 							element.pattern = patternToRegExp(elementName);
2651 							patternElements.push(element);
2652 						} else
2653 							elements[elementName] = element;
2654 					}
2655 				}
2656 			}
2657 		};
2658 
2659 		function setValidElements(valid_elements) {
2660 			elements = {};
2661 			patternElements = [];
2662 
2663 			addValidElements(valid_elements);
2664 
2665 			each(schemaItems, function(element, name) {
2666 				children[name] = element.children;
2667 			});
2668 		};
2669 
2670 		// Adds custom non HTML elements to the schema
2671 		function addCustomElements(custom_elements) {
2672 			var customElementRegExp = /^(~)?(.+)$/;
2673 
2674 			if (custom_elements) {
2675 				each(split(custom_elements), function(rule) {
2676 					var matches = customElementRegExp.exec(rule),
2677 						inline = matches[1] === '~',
2678 						cloneName = inline ? 'span' : 'div',
2679 						name = matches[2];
2680 
2681 					children[name] = children[cloneName];
2682 					customElementsMap[name] = cloneName;
2683 
2684 					// If it's not marked as inline then add it to valid block elements
2685 					if (!inline)
2686 						blockElementsMap[name] = {};
2687 
2688 					// Add custom elements at span/div positions
2689 					each(children, function(element, child) {
2690 						if (element[cloneName])
2691 							element[name] = element[cloneName];
2692 					});
2693 				});
2694 			}
2695 		};
2696 
2697 		// Adds valid children to the schema object
2698 		function addValidChildren(valid_children) {
2699 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2700 
2701 			if (valid_children) {
2702 				each(split(valid_children), function(rule) {
2703 					var matches = childRuleRegExp.exec(rule), parent, prefix;
2704 
2705 					if (matches) {
2706 						prefix = matches[1];
2707 
2708 						// Add/remove items from default
2709 						if (prefix)
2710 							parent = children[matches[2]];
2711 						else
2712 							parent = children[matches[2]] = {'#comment' : {}};
2713 
2714 						parent = children[matches[2]];
2715 
2716 						each(split(matches[3], '|'), function(child) {
2717 							if (prefix === '-')
2718 								delete parent[child];
2719 							else
2720 								parent[child] = {};
2721 						});
2722 					}
2723 				});
2724 			}
2725 		};
2726 
2727 		function getElementRule(name) {
2728 			var element = elements[name], i;
2729 
2730 			// Exact match found
2731 			if (element)
2732 				return element;
2733 
2734 			// No exact match then try the patterns
2735 			i = patternElements.length;
2736 			while (i--) {
2737 				element = patternElements[i];
2738 
2739 				if (element.pattern.test(name))
2740 					return element;
2741 			}
2742 		};
2743 
2744 		if (!settings.valid_elements) {
2745 			// No valid elements defined then clone the elements from the schema spec
2746 			each(schemaItems, function(element, name) {
2747 				elements[name] = {
2748 					attributes : element.attributes,
2749 					attributesOrder : element.attributesOrder
2750 				};
2751 
2752 				children[name] = element.children;
2753 			});
2754 
2755 			// Switch these on HTML4
2756 			if (settings.schema != "html5") {
2757 				each(split('strong/b,em/i'), function(item) {
2758 					item = split(item, '/');
2759 					elements[item[1]].outputName = item[0];
2760 				});
2761 			}
2762 
2763 			// Add default alt attribute for images
2764 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
2765 
2766 			// Remove these if they are empty by default
2767 			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
2768 				if (elements[name]) {
2769 					elements[name].removeEmpty = true;
2770 				}
2771 			});
2772 
2773 			// Padd these by default
2774 			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2775 				elements[name].paddEmpty = true;
2776 			});
2777 		} else
2778 			setValidElements(settings.valid_elements);
2779 
2780 		addCustomElements(settings.custom_elements);
2781 		addValidChildren(settings.valid_children);
2782 		addValidElements(settings.extended_valid_elements);
2783 
2784 		// Todo: Remove this when we fix list handling to be valid
2785 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2786 
2787 		// Delete invalid elements
2788 		if (settings.invalid_elements) {
2789 			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2790 				if (elements[item])
2791 					delete elements[item];
2792 			});
2793 		}
2794 
2795 		// If the user didn't allow span only allow internal spans
2796 		if (!getElementRule('span'))
2797 			addValidElements('span[!data-mce-type|*]');
2798 
2799 		self.children = children;
2800 
2801 		self.styles = validStyles;
2802 
2803 		self.getBoolAttrs = function() {
2804 			return boolAttrMap;
2805 		};
2806 
2807 		self.getBlockElements = function() {
2808 			return blockElementsMap;
2809 		};
2810 
2811 		self.getShortEndedElements = function() {
2812 			return shortEndedElementsMap;
2813 		};
2814 
2815 		self.getSelfClosingElements = function() {
2816 			return selfClosingElementsMap;
2817 		};
2818 
2819 		self.getNonEmptyElements = function() {
2820 			return nonEmptyElementsMap;
2821 		};
2822 
2823 		self.getWhiteSpaceElements = function() {
2824 			return whiteSpaceElementsMap;
2825 		};
2826 
2827 		self.isValidChild = function(name, child) {
2828 			var parent = children[name];
2829 
2830 			return !!(parent && parent[child]);
2831 		};
2832 
2833 		self.isValid = function(name, attr) {
2834 			var attrPatterns, i, rule = getElementRule(name);
2835 
2836 			// Check if it's a valid element
2837 			if (rule) {
2838 				if (attr) {
2839 					// Check if attribute name exists
2840 					if (rule.attributes[attr]) {
2841 						return true;
2842 					}
2843 
2844 					// Check if attribute matches a regexp pattern
2845 					attrPatterns = rule.attributePatterns;
2846 					if (attrPatterns) {
2847 						i = attrPatterns.length;
2848 						while (i--) {
2849 							if (attrPatterns[i].pattern.test(name)) {
2850 								return true;
2851 							}
2852 						}
2853 					}
2854 				} else {
2855 					return true;
2856 				}
2857 			}
2858 
2859 			// No match
2860 			return false;
2861 		};
2862 		
2863 		self.getElementRule = getElementRule;
2864 
2865 		self.getCustomElements = function() {
2866 			return customElementsMap;
2867 		};
2868 
2869 		self.addValidElements = addValidElements;
2870 
2871 		self.setValidElements = setValidElements;
2872 
2873 		self.addCustomElements = addCustomElements;
2874 
2875 		self.addValidChildren = addValidChildren;
2876 	};
2877 })(tinymce);
2878 
2879 (function(tinymce) {
2880 	tinymce.html.SaxParser = function(settings, schema) {
2881 		var self = this, noop = function() {};
2882 
2883 		settings = settings || {};
2884 		self.schema = schema = schema || new tinymce.html.Schema();
2885 
2886 		if (settings.fix_self_closing !== false)
2887 			settings.fix_self_closing = true;
2888 
2889 		// Add handler functions from settings and setup default handlers
2890 		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2891 			if (name)
2892 				self[name] = settings[name] || noop;
2893 		});
2894 
2895 		self.parse = function(html) {
2896 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2897 				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
2898 				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2899 				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
2900 
2901 			function processEndTag(name) {
2902 				var pos, i;
2903 
2904 				// Find position of parent of the same type
2905 				pos = stack.length;
2906 				while (pos--) {
2907 					if (stack[pos].name === name)
2908 						break;						
2909 				}
2910 
2911 				// Found parent
2912 				if (pos >= 0) {
2913 					// Close all the open elements
2914 					for (i = stack.length - 1; i >= pos; i--) {
2915 						name = stack[i];
2916 
2917 						if (name.valid)
2918 							self.end(name.name);
2919 					}
2920 
2921 					// Remove the open elements from the stack
2922 					stack.length = pos;
2923 				}
2924 			};
2925 
2926 			function parseAttribute(match, name, value, val2, val3) {
2927 				var attrRule, i;
2928 
2929 				name = name.toLowerCase();
2930 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2931 
2932 				// Validate name and value
2933 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2934 					attrRule = validAttributesMap[name];
2935 
2936 					// Find rule by pattern matching
2937 					if (!attrRule && validAttributePatterns) {
2938 						i = validAttributePatterns.length;
2939 						while (i--) {
2940 							attrRule = validAttributePatterns[i];
2941 							if (attrRule.pattern.test(name))
2942 								break;
2943 						}
2944 
2945 						// No rule matched
2946 						if (i === -1)
2947 							attrRule = null;
2948 					}
2949 
2950 					// No attribute rule found
2951 					if (!attrRule)
2952 						return;
2953 
2954 					// Validate value
2955 					if (attrRule.validValues && !(value in attrRule.validValues))
2956 						return;
2957 				}
2958 
2959 				// Add attribute to list and map
2960 				attrList.map[name] = value;
2961 				attrList.push({
2962 					name: name,
2963 					value: value
2964 				});
2965 			};
2966 
2967 			// Precompile RegExps and map objects
2968 			tokenRegExp = new RegExp('<(?:' +
2969 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
2970 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2971 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2972 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2973 				'(?:\\/([^>]+)>)|' + // End element
2974 				'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
2975 			')', 'g');
2976 
2977 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2978 			specialElements = {
2979 				'script' : /<\/script[^>]*>/gi,
2980 				'style' : /<\/style[^>]*>/gi,
2981 				'noscript' : /<\/noscript[^>]*>/gi
2982 			};
2983 
2984 			// Setup lookup tables for empty elements and boolean attributes
2985 			shortEndedElements = schema.getShortEndedElements();
2986 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
2987 			fillAttrsMap = schema.getBoolAttrs();
2988 			validate = settings.validate;
2989 			removeInternalElements = settings.remove_internals;
2990 			fixSelfClosing = settings.fix_self_closing;
2991 			isIE = tinymce.isIE;
2992 			invalidPrefixRegExp = /^:/;
2993 
2994 			while (matches = tokenRegExp.exec(html)) {
2995 				// Text
2996 				if (index < matches.index)
2997 					self.text(decode(html.substr(index, matches.index - index)));
2998 
2999 				if (value = matches[6]) { // End element
3000 					value = value.toLowerCase();
3001 
3002 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3003 					if (isIE && invalidPrefixRegExp.test(value))
3004 						value = value.substr(1);
3005 
3006 					processEndTag(value);
3007 				} else if (value = matches[7]) { // Start element
3008 					value = value.toLowerCase();
3009 
3010 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3011 					if (isIE && invalidPrefixRegExp.test(value))
3012 						value = value.substr(1);
3013 
3014 					isShortEnded = value in shortEndedElements;
3015 
3016 					// Is self closing tag for example an <li> after an open <li>
3017 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3018 						processEndTag(value);
3019 
3020 					// Validate element
3021 					if (!validate || (elementRule = schema.getElementRule(value))) {
3022 						isValidElement = true;
3023 
3024 						// Grab attributes map and patters when validation is enabled
3025 						if (validate) {
3026 							validAttributesMap = elementRule.attributes;
3027 							validAttributePatterns = elementRule.attributePatterns;
3028 						}
3029 
3030 						// Parse attributes
3031 						if (attribsValue = matches[8]) {
3032 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3033 
3034 							// If the element has internal attributes then remove it if we are told to do so
3035 							if (isInternalElement && removeInternalElements)
3036 								isValidElement = false;
3037 
3038 							attrList = [];
3039 							attrList.map = {};
3040 
3041 							attribsValue.replace(attrRegExp, parseAttribute);
3042 						} else {
3043 							attrList = [];
3044 							attrList.map = {};
3045 						}
3046 
3047 						// Process attributes if validation is enabled
3048 						if (validate && !isInternalElement) {
3049 							attributesRequired = elementRule.attributesRequired;
3050 							attributesDefault = elementRule.attributesDefault;
3051 							attributesForced = elementRule.attributesForced;
3052 
3053 							// Handle forced attributes
3054 							if (attributesForced) {
3055 								i = attributesForced.length;
3056 								while (i--) {
3057 									attr = attributesForced[i];
3058 									name = attr.name;
3059 									attrValue = attr.value;
3060 
3061 									if (attrValue === '{$uid}')
3062 										attrValue = 'mce_' + idCount++;
3063 
3064 									attrList.map[name] = attrValue;
3065 									attrList.push({name: name, value: attrValue});
3066 								}
3067 							}
3068 
3069 							// Handle default attributes
3070 							if (attributesDefault) {
3071 								i = attributesDefault.length;
3072 								while (i--) {
3073 									attr = attributesDefault[i];
3074 									name = attr.name;
3075 
3076 									if (!(name in attrList.map)) {
3077 										attrValue = attr.value;
3078 
3079 										if (attrValue === '{$uid}')
3080 											attrValue = 'mce_' + idCount++;
3081 
3082 										attrList.map[name] = attrValue;
3083 										attrList.push({name: name, value: attrValue});
3084 									}
3085 								}
3086 							}
3087 
3088 							// Handle required attributes
3089 							if (attributesRequired) {
3090 								i = attributesRequired.length;
3091 								while (i--) {
3092 									if (attributesRequired[i] in attrList.map)
3093 										break;
3094 								}
3095 
3096 								// None of the required attributes where found
3097 								if (i === -1)
3098 									isValidElement = false;
3099 							}
3100 
3101 							// Invalidate element if it's marked as bogus
3102 							if (attrList.map['data-mce-bogus'])
3103 								isValidElement = false;
3104 						}
3105 
3106 						if (isValidElement)
3107 							self.start(value, attrList, isShortEnded);
3108 					} else
3109 						isValidElement = false;
3110 
3111 					// Treat script, noscript and style a bit different since they may include code that looks like elements
3112 					if (endRegExp = specialElements[value]) {
3113 						endRegExp.lastIndex = index = matches.index + matches[0].length;
3114 
3115 						if (matches = endRegExp.exec(html)) {
3116 							if (isValidElement)
3117 								text = html.substr(index, matches.index - index);
3118 
3119 							index = matches.index + matches[0].length;
3120 						} else {
3121 							text = html.substr(index);
3122 							index = html.length;
3123 						}
3124 
3125 						if (isValidElement && text.length > 0)
3126 							self.text(text, true);
3127 
3128 						if (isValidElement)
3129 							self.end(value);
3130 
3131 						tokenRegExp.lastIndex = index;
3132 						continue;
3133 					}
3134 
3135 					// Push value on to stack
3136 					if (!isShortEnded) {
3137 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3138 							stack.push({name: value, valid: isValidElement});
3139 						else if (isValidElement)
3140 							self.end(value);
3141 					}
3142 				} else if (value = matches[1]) { // Comment
3143 					self.comment(value);
3144 				} else if (value = matches[2]) { // CDATA
3145 					self.cdata(value);
3146 				} else if (value = matches[3]) { // DOCTYPE
3147 					self.doctype(value);
3148 				} else if (value = matches[4]) { // PI
3149 					self.pi(value, matches[5]);
3150 				}
3151 
3152 				index = matches.index + matches[0].length;
3153 			}
3154 
3155 			// Text
3156 			if (index < html.length)
3157 				self.text(decode(html.substr(index)));
3158 
3159 			// Close any open elements
3160 			for (i = stack.length - 1; i >= 0; i--) {
3161 				value = stack[i];
3162 
3163 				if (value.valid)
3164 					self.end(value.name);
3165 			}
3166 		};
3167 	}
3168 })(tinymce);
3169 
3170 (function(tinymce) {
3171 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3172 		'#text' : 3,
3173 		'#comment' : 8,
3174 		'#cdata' : 4,
3175 		'#pi' : 7,
3176 		'#doctype' : 10,
3177 		'#document-fragment' : 11
3178 	};
3179 
3180 	// Walks the tree left/right
3181 	function walk(node, root_node, prev) {
3182 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3183 
3184 		// Walk into nodes if it has a start
3185 		if (node[startName])
3186 			return node[startName];
3187 
3188 		// Return the sibling if it has one
3189 		if (node !== root_node) {
3190 			sibling = node[siblingName];
3191 
3192 			if (sibling)
3193 				return sibling;
3194 
3195 			// Walk up the parents to look for siblings
3196 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3197 				sibling = parent[siblingName];
3198 
3199 				if (sibling)
3200 					return sibling;
3201 			}
3202 		}
3203 	};
3204 
3205 	function Node(name, type) {
3206 		this.name = name;
3207 		this.type = type;
3208 
3209 		if (type === 1) {
3210 			this.attributes = [];
3211 			this.attributes.map = {};
3212 		}
3213 	}
3214 
3215 	tinymce.extend(Node.prototype, {
3216 		replace : function(node) {
3217 			var self = this;
3218 
3219 			if (node.parent)
3220 				node.remove();
3221 
3222 			self.insert(node, self);
3223 			self.remove();
3224 
3225 			return self;
3226 		},
3227 
3228 		attr : function(name, value) {
3229 			var self = this, attrs, i, undef;
3230 
3231 			if (typeof name !== "string") {
3232 				for (i in name)
3233 					self.attr(i, name[i]);
3234 
3235 				return self;
3236 			}
3237 
3238 			if (attrs = self.attributes) {
3239 				if (value !== undef) {
3240 					// Remove attribute
3241 					if (value === null) {
3242 						if (name in attrs.map) {
3243 							delete attrs.map[name];
3244 
3245 							i = attrs.length;
3246 							while (i--) {
3247 								if (attrs[i].name === name) {
3248 									attrs = attrs.splice(i, 1);
3249 									return self;
3250 								}
3251 							}
3252 						}
3253 
3254 						return self;
3255 					}
3256 
3257 					// Set attribute
3258 					if (name in attrs.map) {
3259 						// Set attribute
3260 						i = attrs.length;
3261 						while (i--) {
3262 							if (attrs[i].name === name) {
3263 								attrs[i].value = value;
3264 								break;
3265 							}
3266 						}
3267 					} else
3268 						attrs.push({name: name, value: value});
3269 
3270 					attrs.map[name] = value;
3271 
3272 					return self;
3273 				} else {
3274 					return attrs.map[name];
3275 				}
3276 			}
3277 		},
3278 
3279 		clone : function() {
3280 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3281 
3282 			// Clone element attributes
3283 			if (selfAttrs = self.attributes) {
3284 				cloneAttrs = [];
3285 				cloneAttrs.map = {};
3286 
3287 				for (i = 0, l = selfAttrs.length; i < l; i++) {
3288 					selfAttr = selfAttrs[i];
3289 
3290 					// Clone everything except id
3291 					if (selfAttr.name !== 'id') {
3292 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3293 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3294 					}
3295 				}
3296 
3297 				clone.attributes = cloneAttrs;
3298 			}
3299 
3300 			clone.value = self.value;
3301 			clone.shortEnded = self.shortEnded;
3302 
3303 			return clone;
3304 		},
3305 
3306 		wrap : function(wrapper) {
3307 			var self = this;
3308 
3309 			self.parent.insert(wrapper, self);
3310 			wrapper.append(self);
3311 
3312 			return self;
3313 		},
3314 
3315 		unwrap : function() {
3316 			var self = this, node, next;
3317 
3318 			for (node = self.firstChild; node; ) {
3319 				next = node.next;
3320 				self.insert(node, self, true);
3321 				node = next;
3322 			}
3323 
3324 			self.remove();
3325 		},
3326 
3327 		remove : function() {
3328 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3329 
3330 			if (parent) {
3331 				if (parent.firstChild === self) {
3332 					parent.firstChild = next;
3333 
3334 					if (next)
3335 						next.prev = null;
3336 				} else {
3337 					prev.next = next;
3338 				}
3339 
3340 				if (parent.lastChild === self) {
3341 					parent.lastChild = prev;
3342 
3343 					if (prev)
3344 						prev.next = null;
3345 				} else {
3346 					next.prev = prev;
3347 				}
3348 
3349 				self.parent = self.next = self.prev = null;
3350 			}
3351 
3352 			return self;
3353 		},
3354 
3355 		append : function(node) {
3356 			var self = this, last;
3357 
3358 			if (node.parent)
3359 				node.remove();
3360 
3361 			last = self.lastChild;
3362 			if (last) {
3363 				last.next = node;
3364 				node.prev = last;
3365 				self.lastChild = node;
3366 			} else
3367 				self.lastChild = self.firstChild = node;
3368 
3369 			node.parent = self;
3370 
3371 			return node;
3372 		},
3373 
3374 		insert : function(node, ref_node, before) {
3375 			var parent;
3376 
3377 			if (node.parent)
3378 				node.remove();
3379 
3380 			parent = ref_node.parent || this;
3381 
3382 			if (before) {
3383 				if (ref_node === parent.firstChild)
3384 					parent.firstChild = node;
3385 				else
3386 					ref_node.prev.next = node;
3387 
3388 				node.prev = ref_node.prev;
3389 				node.next = ref_node;
3390 				ref_node.prev = node;
3391 			} else {
3392 				if (ref_node === parent.lastChild)
3393 					parent.lastChild = node;
3394 				else
3395 					ref_node.next.prev = node;
3396 
3397 				node.next = ref_node.next;
3398 				node.prev = ref_node;
3399 				ref_node.next = node;
3400 			}
3401 
3402 			node.parent = parent;
3403 
3404 			return node;
3405 		},
3406 
3407 		getAll : function(name) {
3408 			var self = this, node, collection = [];
3409 
3410 			for (node = self.firstChild; node; node = walk(node, self)) {
3411 				if (node.name === name)
3412 					collection.push(node);
3413 			}
3414 
3415 			return collection;
3416 		},
3417 
3418 		empty : function() {
3419 			var self = this, nodes, i, node;
3420 
3421 			// Remove all children
3422 			if (self.firstChild) {
3423 				nodes = [];
3424 
3425 				// Collect the children
3426 				for (node = self.firstChild; node; node = walk(node, self))
3427 					nodes.push(node);
3428 
3429 				// Remove the children
3430 				i = nodes.length;
3431 				while (i--) {
3432 					node = nodes[i];
3433 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3434 				}
3435 			}
3436 
3437 			self.firstChild = self.lastChild = null;
3438 
3439 			return self;
3440 		},
3441 
3442 		isEmpty : function(elements) {
3443 			var self = this, node = self.firstChild, i, name;
3444 
3445 			if (node) {
3446 				do {
3447 					if (node.type === 1) {
3448 						// Ignore bogus elements
3449 						if (node.attributes.map['data-mce-bogus'])
3450 							continue;
3451 
3452 						// Keep empty elements like <img />
3453 						if (elements[node.name])
3454 							return false;
3455 
3456 						// Keep elements with data attributes or name attribute like <a name="1"></a>
3457 						i = node.attributes.length;
3458 						while (i--) {
3459 							name = node.attributes[i].name;
3460 							if (name === "name" || name.indexOf('data-') === 0)
3461 								return false;
3462 						}
3463 					}
3464 
3465 					// Keep comments
3466 					if (node.type === 8)
3467 						return false;
3468 					
3469 					// Keep non whitespace text nodes
3470 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3471 						return false;
3472 				} while (node = walk(node, self));
3473 			}
3474 
3475 			return true;
3476 		},
3477 
3478 		walk : function(prev) {
3479 			return walk(this, null, prev);
3480 		}
3481 	});
3482 
3483 	tinymce.extend(Node, {
3484 		create : function(name, attrs) {
3485 			var node, attrName;
3486 
3487 			// Create node
3488 			node = new Node(name, typeLookup[name] || 1);
3489 
3490 			// Add attributes if needed
3491 			if (attrs) {
3492 				for (attrName in attrs)
3493 					node.attr(attrName, attrs[attrName]);
3494 			}
3495 
3496 			return node;
3497 		}
3498 	});
3499 
3500 	tinymce.html.Node = Node;
3501 })(tinymce);
3502 
3503 (function(tinymce) {
3504 	var Node = tinymce.html.Node;
3505 
3506 	tinymce.html.DomParser = function(settings, schema) {
3507 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3508 
3509 		settings = settings || {};
3510 		settings.validate = "validate" in settings ? settings.validate : true;
3511 		settings.root_name = settings.root_name || 'body';
3512 		self.schema = schema = schema || new tinymce.html.Schema();
3513 
3514 		function fixInvalidChildren(nodes) {
3515 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3516 				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3517 
3518 			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3519 			nonEmptyElements = schema.getNonEmptyElements();
3520 
3521 			for (ni = 0; ni < nodes.length; ni++) {
3522 				node = nodes[ni];
3523 
3524 				// Already removed
3525 				if (!node.parent)
3526 					continue;
3527 
3528 				// Get list of all parent nodes until we find a valid parent to stick the child into
3529 				parents = [node];
3530 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3531 					parents.push(parent);
3532 
3533 				// Found a suitable parent
3534 				if (parent && parents.length > 1) {
3535 					// Reverse the array since it makes looping easier
3536 					parents.reverse();
3537 
3538 					// Clone the related parent and insert that after the moved node
3539 					newParent = currentNode = self.filterNode(parents[0].clone());
3540 
3541 					// Start cloning and moving children on the left side of the target node
3542 					for (i = 0; i < parents.length - 1; i++) {
3543 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3544 							tempNode = self.filterNode(parents[i].clone());
3545 							currentNode.append(tempNode);
3546 						} else
3547 							tempNode = currentNode;
3548 
3549 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3550 							nextNode = childNode.next;
3551 							tempNode.append(childNode);
3552 							childNode = nextNode;
3553 						}
3554 
3555 						currentNode = tempNode;
3556 					}
3557 
3558 					if (!newParent.isEmpty(nonEmptyElements)) {
3559 						parent.insert(newParent, parents[0], true);
3560 						parent.insert(node, newParent);
3561 					} else {
3562 						parent.insert(node, parents[0], true);
3563 					}
3564 
3565 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3566 					parent = parents[0];
3567 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3568 						parent.empty().remove();
3569 					}
3570 				} else if (node.parent) {
3571 					// If it's an LI try to find a UL/OL for it or wrap it
3572 					if (node.name === 'li') {
3573 						sibling = node.prev;
3574 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3575 							sibling.append(node);
3576 							continue;
3577 						}
3578 
3579 						sibling = node.next;
3580 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3581 							sibling.insert(node, sibling.firstChild, true);
3582 							continue;
3583 						}
3584 
3585 						node.wrap(self.filterNode(new Node('ul', 1)));
3586 						continue;
3587 					}
3588 
3589 					// Try wrapping the element in a DIV
3590 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3591 						node.wrap(self.filterNode(new Node('div', 1)));
3592 					} else {
3593 						// We failed wrapping it, then remove or unwrap it
3594 						if (node.name === 'style' || node.name === 'script')
3595 							node.empty().remove();
3596 						else
3597 							node.unwrap();
3598 					}
3599 				}
3600 			}
3601 		};
3602 
3603 		self.filterNode = function(node) {
3604 			var i, name, list;
3605 
3606 			// Run element filters
3607 			if (name in nodeFilters) {
3608 				list = matchedNodes[name];
3609 
3610 				if (list)
3611 					list.push(node);
3612 				else
3613 					matchedNodes[name] = [node];
3614 			}
3615 
3616 			// Run attribute filters
3617 			i = attributeFilters.length;
3618 			while (i--) {
3619 				name = attributeFilters[i].name;
3620 
3621 				if (name in node.attributes.map) {
3622 					list = matchedAttributes[name];
3623 
3624 					if (list)
3625 						list.push(node);
3626 					else
3627 						matchedAttributes[name] = [node];
3628 				}
3629 			}
3630 
3631 			return node;
3632 		};
3633 
3634 		self.addNodeFilter = function(name, callback) {
3635 			tinymce.each(tinymce.explode(name), function(name) {
3636 				var list = nodeFilters[name];
3637 
3638 				if (!list)
3639 					nodeFilters[name] = list = [];
3640 
3641 				list.push(callback);
3642 			});
3643 		};
3644 
3645 		self.addAttributeFilter = function(name, callback) {
3646 			tinymce.each(tinymce.explode(name), function(name) {
3647 				var i;
3648 
3649 				for (i = 0; i < attributeFilters.length; i++) {
3650 					if (attributeFilters[i].name === name) {
3651 						attributeFilters[i].callbacks.push(callback);
3652 						return;
3653 					}
3654 				}
3655 
3656 				attributeFilters.push({name: name, callbacks: [callback]});
3657 			});
3658 		};
3659 
3660 		self.parse = function(html, args) {
3661 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3662 				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3663 				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3664 
3665 			args = args || {};
3666 			matchedNodes = {};
3667 			matchedAttributes = {};
3668 			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3669 			nonEmptyElements = schema.getNonEmptyElements();
3670 			children = schema.children;
3671 			validate = settings.validate;
3672 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3673 
3674 			whiteSpaceElements = schema.getWhiteSpaceElements();
3675 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3676 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3677 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3678 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3679 
3680 			function addRootBlocks() {
3681 				var node = rootNode.firstChild, next, rootBlockNode;
3682 
3683 				while (node) {
3684 					next = node.next;
3685 
3686 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3687 						if (!rootBlockNode) {
3688 							// Create a new root block element
3689 							rootBlockNode = createNode(rootBlockName, 1);
3690 							rootNode.insert(rootBlockNode, node);
3691 							rootBlockNode.append(node);
3692 						} else
3693 							rootBlockNode.append(node);
3694 					} else {
3695 						rootBlockNode = null;
3696 					}
3697 
3698 					node = next;
3699 				};
3700 			};
3701 
3702 			function createNode(name, type) {
3703 				var node = new Node(name, type), list;
3704 
3705 				if (name in nodeFilters) {
3706 					list = matchedNodes[name];
3707 
3708 					if (list)
3709 						list.push(node);
3710 					else
3711 						matchedNodes[name] = [node];
3712 				}
3713 
3714 				return node;
3715 			};
3716 
3717 			function removeWhitespaceBefore(node) {
3718 				var textNode, textVal, sibling;
3719 
3720 				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3721 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3722 
3723 					if (textVal.length > 0) {
3724 						textNode.value = textVal;
3725 						textNode = textNode.prev;
3726 					} else {
3727 						sibling = textNode.prev;
3728 						textNode.remove();
3729 						textNode = sibling;
3730 					}
3731 				}
3732 			};
3733 
3734 			function cloneAndExcludeBlocks(input) {
3735 				var name, output = {};
3736 
3737 				for (name in input) {
3738 					if (name !== 'li' && name != 'p') {
3739 						output[name] = input[name];
3740 					}
3741 				}
3742 
3743 				return output;
3744 			};
3745 
3746 			parser = new tinymce.html.SaxParser({
3747 				validate : validate,
3748 
3749 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
3750 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
3751 
3752 				cdata: function(text) {
3753 					node.append(createNode('#cdata', 4)).value = text;
3754 				},
3755 
3756 				text: function(text, raw) {
3757 					var textNode;
3758 
3759 					// Trim all redundant whitespace on non white space elements
3760 					if (!isInWhiteSpacePreservedElement) {
3761 						text = text.replace(allWhiteSpaceRegExp, ' ');
3762 
3763 						if (node.lastChild && blockElements[node.lastChild.name])
3764 							text = text.replace(startWhiteSpaceRegExp, '');
3765 					}
3766 
3767 					// Do we need to create the node
3768 					if (text.length !== 0) {
3769 						textNode = createNode('#text', 3);
3770 						textNode.raw = !!raw;
3771 						node.append(textNode).value = text;
3772 					}
3773 				},
3774 
3775 				comment: function(text) {
3776 					node.append(createNode('#comment', 8)).value = text;
3777 				},
3778 
3779 				pi: function(name, text) {
3780 					node.append(createNode(name, 7)).value = text;
3781 					removeWhitespaceBefore(node);
3782 				},
3783 
3784 				doctype: function(text) {
3785 					var newNode;
3786 		
3787 					newNode = node.append(createNode('#doctype', 10));
3788 					newNode.value = text;
3789 					removeWhitespaceBefore(node);
3790 				},
3791 
3792 				start: function(name, attrs, empty) {
3793 					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3794 
3795 					elementRule = validate ? schema.getElementRule(name) : {};
3796 					if (elementRule) {
3797 						newNode = createNode(elementRule.outputName || name, 1);
3798 						newNode.attributes = attrs;
3799 						newNode.shortEnded = empty;
3800 
3801 						node.append(newNode);
3802 
3803 						// Check if node is valid child of the parent node is the child is
3804 						// unknown we don't collect it since it's probably a custom element
3805 						parent = children[node.name];
3806 						if (parent && children[newNode.name] && !parent[newNode.name])
3807 							invalidChildren.push(newNode);
3808 
3809 						attrFiltersLen = attributeFilters.length;
3810 						while (attrFiltersLen--) {
3811 							attrName = attributeFilters[attrFiltersLen].name;
3812 
3813 							if (attrName in attrs.map) {
3814 								list = matchedAttributes[attrName];
3815 
3816 								if (list)
3817 									list.push(newNode);
3818 								else
3819 									matchedAttributes[attrName] = [newNode];
3820 							}
3821 						}
3822 
3823 						// Trim whitespace before block
3824 						if (blockElements[name])
3825 							removeWhitespaceBefore(newNode);
3826 
3827 						// Change current node if the element wasn't empty i.e not <br /> or <img />
3828 						if (!empty)
3829 							node = newNode;
3830 
3831 						// Check if we are inside a whitespace preserved element
3832 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3833 							isInWhiteSpacePreservedElement = true;
3834 						}
3835 					}
3836 				},
3837 
3838 				end: function(name) {
3839 					var textNode, elementRule, text, sibling, tempNode;
3840 
3841 					elementRule = validate ? schema.getElementRule(name) : {};
3842 					if (elementRule) {
3843 						if (blockElements[name]) {
3844 							if (!isInWhiteSpacePreservedElement) {
3845 								// Trim whitespace of the first node in a block
3846 								textNode = node.firstChild;
3847 								if (textNode && textNode.type === 3) {
3848 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
3849 
3850 									// Any characters left after trim or should we remove it
3851 									if (text.length > 0) {
3852 										textNode.value = text;
3853 										textNode = textNode.next;
3854 									} else {
3855 										sibling = textNode.next;
3856 										textNode.remove();
3857 										textNode = sibling;
3858 									}
3859 
3860 									// Remove any pure whitespace siblings
3861 									while (textNode && textNode.type === 3) {
3862 										text = textNode.value;
3863 										sibling = textNode.next;
3864 
3865 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3866 											textNode.remove();
3867 											textNode = sibling;
3868 										}
3869 
3870 										textNode = sibling;
3871 									}
3872 								}
3873 
3874 								// Trim whitespace of the last node in a block
3875 								textNode = node.lastChild;
3876 								if (textNode && textNode.type === 3) {
3877 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
3878 
3879 									// Any characters left after trim or should we remove it
3880 									if (text.length > 0) {
3881 										textNode.value = text;
3882 										textNode = textNode.prev;
3883 									} else {
3884 										sibling = textNode.prev;
3885 										textNode.remove();
3886 										textNode = sibling;
3887 									}
3888 
3889 									// Remove any pure whitespace siblings
3890 									while (textNode && textNode.type === 3) {
3891 										text = textNode.value;
3892 										sibling = textNode.prev;
3893 
3894 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3895 											textNode.remove();
3896 											textNode = sibling;
3897 										}
3898 
3899 										textNode = sibling;
3900 									}
3901 								}
3902 							}
3903 
3904 							// Trim start white space
3905 							textNode = node.prev;
3906 							if (textNode && textNode.type === 3) {
3907 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
3908 
3909 								if (text.length > 0)
3910 									textNode.value = text;
3911 								else
3912 									textNode.remove();
3913 							}
3914 						}
3915 
3916 						// Check if we exited a whitespace preserved element
3917 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3918 							isInWhiteSpacePreservedElement = false;
3919 						}
3920 
3921 						// Handle empty nodes
3922 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
3923 							if (node.isEmpty(nonEmptyElements)) {
3924 								if (elementRule.paddEmpty)
3925 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
3926 								else {
3927 									// Leave nodes that have a name like <a name="name">
3928 									if (!node.attributes.map.name && !node.attributes.map.id) {
3929 										tempNode = node.parent;
3930 										node.empty().remove();
3931 										node = tempNode;
3932 										return;
3933 									}
3934 								}
3935 							}
3936 						}
3937 
3938 						node = node.parent;
3939 					}
3940 				}
3941 			}, schema);
3942 
3943 			rootNode = node = new Node(args.context || settings.root_name, 11);
3944 
3945 			parser.parse(html);
3946 
3947 			// Fix invalid children or report invalid children in a contextual parsing
3948 			if (validate && invalidChildren.length) {
3949 				if (!args.context)
3950 					fixInvalidChildren(invalidChildren);
3951 				else
3952 					args.invalid = true;
3953 			}
3954 
3955 			// Wrap nodes in the root into block elements if the root is body
3956 			if (rootBlockName && rootNode.name == 'body')
3957 				addRootBlocks();
3958 
3959 			// Run filters only when the contents is valid
3960 			if (!args.invalid) {
3961 				// Run node filters
3962 				for (name in matchedNodes) {
3963 					list = nodeFilters[name];
3964 					nodes = matchedNodes[name];
3965 
3966 					// Remove already removed children
3967 					fi = nodes.length;
3968 					while (fi--) {
3969 						if (!nodes[fi].parent)
3970 							nodes.splice(fi, 1);
3971 					}
3972 
3973 					for (i = 0, l = list.length; i < l; i++)
3974 						list[i](nodes, name, args);
3975 				}
3976 
3977 				// Run attribute filters
3978 				for (i = 0, l = attributeFilters.length; i < l; i++) {
3979 					list = attributeFilters[i];
3980 
3981 					if (list.name in matchedAttributes) {
3982 						nodes = matchedAttributes[list.name];
3983 
3984 						// Remove already removed children
3985 						fi = nodes.length;
3986 						while (fi--) {
3987 							if (!nodes[fi].parent)
3988 								nodes.splice(fi, 1);
3989 						}
3990 
3991 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
3992 							list.callbacks[fi](nodes, list.name, args);
3993 					}
3994 				}
3995 			}
3996 
3997 			return rootNode;
3998 		};
3999 
4000 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4001 		// make it possible to place the caret inside empty blocks. This logic tries to remove
4002 		// these elements and keep br elements that where intended to be there intact
4003 		if (settings.remove_trailing_brs) {
4004 			self.addNodeFilter('br', function(nodes, name) {
4005 				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4006 					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4007 
4008 				// Remove brs from body element as well
4009 				blockElements.body = 1;
4010 
4011 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4012 				for (i = 0; i < l; i++) {
4013 					node = nodes[i];
4014 					parent = node.parent;
4015 
4016 					if (blockElements[node.parent.name] && node === parent.lastChild) {
4017 						// Loop all nodes to the left of the current node and check for other BR elements
4018 						// excluding bookmarks since they are invisible
4019 						prev = node.prev;
4020 						while (prev) {
4021 							prevName = prev.name;
4022 
4023 							// Ignore bookmarks
4024 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4025 								// Found a non BR element
4026 								if (prevName !== "br")
4027 									break;
4028 	
4029 								// Found another br it's a <br><br> structure then don't remove anything
4030 								if (prevName === 'br') {
4031 									node = null;
4032 									break;
4033 								}
4034 							}
4035 
4036 							prev = prev.prev;
4037 						}
4038 
4039 						if (node) {
4040 							node.remove();
4041 
4042 							// Is the parent to be considered empty after we removed the BR
4043 							if (parent.isEmpty(nonEmptyElements)) {
4044 								elementRule = schema.getElementRule(parent.name);
4045 
4046 								// Remove or padd the element depending on schema rule
4047 								if (elementRule) {
4048 									if (elementRule.removeEmpty)
4049 										parent.remove();
4050 									else if (elementRule.paddEmpty)
4051 										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4052 								}
4053 							}
4054 						}
4055 					} else {
4056 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 
4057 						lastParent = node;
4058 						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4059 							lastParent = parent;
4060 
4061 							if (blockElements[parent.name]) {
4062 								break;
4063 							}
4064 
4065 							parent = parent.parent;
4066 						}
4067 
4068 						if (lastParent === parent) {
4069 							textNode = new tinymce.html.Node('#text', 3);
4070 							textNode.value = '\u00a0';
4071 							node.replace(textNode);
4072 						}
4073 					}
4074 				}
4075 			});
4076 		}
4077 
4078 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4079 		if (!settings.allow_html_in_named_anchor) {
4080 			self.addAttributeFilter('id,name', function(nodes, name) {
4081 				var i = nodes.length, sibling, prevSibling, parent, node;
4082 
4083 				while (i--) {
4084 					node = nodes[i];
4085 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4086 						parent = node.parent;
4087 
4088 						// Move children after current node
4089 						sibling = node.lastChild;
4090 						do {
4091 							prevSibling = sibling.prev;
4092 							parent.insert(sibling, node);
4093 							sibling = prevSibling;
4094 						} while (sibling);
4095 					}
4096 				}
4097 			});
4098 		}
4099 	}
4100 })(tinymce);
4101 
4102 tinymce.html.Writer = function(settings) {
4103 	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4104 
4105 	settings = settings || {};
4106 	indent = settings.indent;
4107 	indentBefore = tinymce.makeMap(settings.indent_before || '');
4108 	indentAfter = tinymce.makeMap(settings.indent_after || '');
4109 	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4110 	htmlOutput = settings.element_format == "html";
4111 
4112 	return {
4113 		start: function(name, attrs, empty) {
4114 			var i, l, attr, value;
4115 
4116 			if (indent && indentBefore[name] && html.length > 0) {
4117 				value = html[html.length - 1];
4118 
4119 				if (value.length > 0 && value !== '\n')
4120 					html.push('\n');
4121 			}
4122 
4123 			html.push('<', name);
4124 
4125 			if (attrs) {
4126 				for (i = 0, l = attrs.length; i < l; i++) {
4127 					attr = attrs[i];
4128 					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4129 				}
4130 			}
4131 
4132 			if (!empty || htmlOutput)
4133 				html[html.length] = '>';
4134 			else
4135 				html[html.length] = ' />';
4136 
4137 			if (empty && indent && indentAfter[name] && html.length > 0) {
4138 				value = html[html.length - 1];
4139 
4140 				if (value.length > 0 && value !== '\n')
4141 					html.push('\n');
4142 			}
4143 		},
4144 
4145 		end: function(name) {
4146 			var value;
4147 
4148 			/*if (indent && indentBefore[name] && html.length > 0) {
4149 				value = html[html.length - 1];
4150 
4151 				if (value.length > 0 && value !== '\n')
4152 					html.push('\n');
4153 			}*/
4154 
4155 			html.push('</', name, '>');
4156 
4157 			if (indent && indentAfter[name] && html.length > 0) {
4158 				value = html[html.length - 1];
4159 
4160 				if (value.length > 0 && value !== '\n')
4161 					html.push('\n');
4162 			}
4163 		},
4164 
4165 		text: function(text, raw) {
4166 			if (text.length > 0)
4167 				html[html.length] = raw ? text : encode(text);
4168 		},
4169 
4170 		cdata: function(text) {
4171 			html.push('<![CDATA[', text, ']]>');
4172 		},
4173 
4174 		comment: function(text) {
4175 			html.push('<!--', text, '-->');
4176 		},
4177 
4178 		pi: function(name, text) {
4179 			if (text)
4180 				html.push('<?', name, ' ', text, '?>');
4181 			else
4182 				html.push('<?', name, '?>');
4183 
4184 			if (indent)
4185 				html.push('\n');
4186 		},
4187 
4188 		doctype: function(text) {
4189 			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4190 		},
4191 
4192 		reset: function() {
4193 			html.length = 0;
4194 		},
4195 
4196 		getContent: function() {
4197 			return html.join('').replace(/\n$/, '');
4198 		}
4199 	};
4200 };
4201 
4202 (function(tinymce) {
4203 	tinymce.html.Serializer = function(settings, schema) {
4204 		var self = this, writer = new tinymce.html.Writer(settings);
4205 
4206 		settings = settings || {};
4207 		settings.validate = "validate" in settings ? settings.validate : true;
4208 
4209 		self.schema = schema = schema || new tinymce.html.Schema();
4210 		self.writer = writer;
4211 
4212 		self.serialize = function(node) {
4213 			var handlers, validate;
4214 
4215 			validate = settings.validate;
4216 
4217 			handlers = {
4218 				// #text
4219 				3: function(node, raw) {
4220 					writer.text(node.value, node.raw);
4221 				},
4222 
4223 				// #comment
4224 				8: function(node) {
4225 					writer.comment(node.value);
4226 				},
4227 
4228 				// Processing instruction
4229 				7: function(node) {
4230 					writer.pi(node.name, node.value);
4231 				},
4232 
4233 				// Doctype
4234 				10: function(node) {
4235 					writer.doctype(node.value);
4236 				},
4237 
4238 				// CDATA
4239 				4: function(node) {
4240 					writer.cdata(node.value);
4241 				},
4242 
4243 				// Document fragment
4244 				11: function(node) {
4245 					if ((node = node.firstChild)) {
4246 						do {
4247 							walk(node);
4248 						} while (node = node.next);
4249 					}
4250 				}
4251 			};
4252 
4253 			writer.reset();
4254 
4255 			function walk(node) {
4256 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4257 
4258 				if (!handler) {
4259 					name = node.name;
4260 					isEmpty = node.shortEnded;
4261 					attrs = node.attributes;
4262 
4263 					// Sort attributes
4264 					if (validate && attrs && attrs.length > 1) {
4265 						sortedAttrs = [];
4266 						sortedAttrs.map = {};
4267 
4268 						elementRule = schema.getElementRule(node.name);
4269 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4270 							attrName = elementRule.attributesOrder[i];
4271 
4272 							if (attrName in attrs.map) {
4273 								attrValue = attrs.map[attrName];
4274 								sortedAttrs.map[attrName] = attrValue;
4275 								sortedAttrs.push({name: attrName, value: attrValue});
4276 							}
4277 						}
4278 
4279 						for (i = 0, l = attrs.length; i < l; i++) {
4280 							attrName = attrs[i].name;
4281 
4282 							if (!(attrName in sortedAttrs.map)) {
4283 								attrValue = attrs.map[attrName];
4284 								sortedAttrs.map[attrName] = attrValue;
4285 								sortedAttrs.push({name: attrName, value: attrValue});
4286 							}
4287 						}
4288 
4289 						attrs = sortedAttrs;
4290 					}
4291 
4292 					writer.start(node.name, attrs, isEmpty);
4293 
4294 					if (!isEmpty) {
4295 						if ((node = node.firstChild)) {
4296 							do {
4297 								walk(node);
4298 							} while (node = node.next);
4299 						}
4300 
4301 						writer.end(name);
4302 					}
4303 				} else
4304 					handler(node);
4305 			}
4306 
4307 			// Serialize element and treat all non elements as fragments
4308 			if (node.type == 1 && !settings.inner)
4309 				walk(node);
4310 			else
4311 				handlers[11](node);
4312 
4313 			return writer.getContent();
4314 		};
4315 	}
4316 })(tinymce);
4317 
4318 // JSLint defined globals
4319 /*global tinymce:false, window:false */
4320 
4321 tinymce.dom = {};
4322 
4323 (function(namespace, expando) {
4324 	var w3cEventModel = !!document.addEventListener;
4325 
4326 	function addEvent(target, name, callback, capture) {
4327 		if (target.addEventListener) {
4328 			target.addEventListener(name, callback, capture || false);
4329 		} else if (target.attachEvent) {
4330 			target.attachEvent('on' + name, callback);
4331 		}
4332 	}
4333 
4334 	function removeEvent(target, name, callback, capture) {
4335 		if (target.removeEventListener) {
4336 			target.removeEventListener(name, callback, capture || false);
4337 		} else if (target.detachEvent) {
4338 			target.detachEvent('on' + name, callback);
4339 		}
4340 	}
4341 
4342 	function fix(original_event, data) {
4343 		var name, event = data || {};
4344 
4345 		// Dummy function that gets replaced on the delegation state functions
4346 		function returnFalse() {
4347 			return false;
4348 		}
4349 
4350 		// Dummy function that gets replaced on the delegation state functions
4351 		function returnTrue() {
4352 			return true;
4353 		}
4354 
4355 		// Copy all properties from the original event
4356 		for (name in original_event) {
4357 			// layerX/layerY is deprecated in Chrome and produces a warning
4358 			if (name !== "layerX" && name !== "layerY") {
4359 				event[name] = original_event[name];
4360 			}
4361 		}
4362 
4363 		// Normalize target IE uses srcElement
4364 		if (!event.target) {
4365 			event.target = event.srcElement || document;
4366 		}
4367 
4368 		// Add preventDefault method
4369 		event.preventDefault = function() {
4370 			event.isDefaultPrevented = returnTrue;
4371 
4372 			// Execute preventDefault on the original event object
4373 			if (original_event) {
4374 				if (original_event.preventDefault) {
4375 					original_event.preventDefault();
4376 				} else {
4377 					original_event.returnValue = false; // IE
4378 				}
4379 			}
4380 		};
4381 
4382 		// Add stopPropagation
4383 		event.stopPropagation = function() {
4384 			event.isPropagationStopped = returnTrue;
4385 
4386 			// Execute stopPropagation on the original event object
4387 			if (original_event) {
4388 				if (original_event.stopPropagation) {
4389 					original_event.stopPropagation();
4390 				} else {
4391 					original_event.cancelBubble = true; // IE
4392 				}
4393 			 }
4394 		};
4395 
4396 		// Add stopImmediatePropagation
4397 		event.stopImmediatePropagation = function() {
4398 			event.isImmediatePropagationStopped = returnTrue;
4399 			event.stopPropagation();
4400 		};
4401 
4402 		// Add event delegation states
4403 		if (!event.isDefaultPrevented) {
4404 			event.isDefaultPrevented = returnFalse;
4405 			event.isPropagationStopped = returnFalse;
4406 			event.isImmediatePropagationStopped = returnFalse;
4407 		}
4408 
4409 		return event;
4410 	}
4411 
4412 	function bindOnReady(win, callback, event_utils) {
4413 		var doc = win.document, event = {type: 'ready'};
4414 
4415 		// Gets called when the DOM is ready
4416 		function readyHandler() {
4417 			if (!event_utils.domLoaded) {
4418 				event_utils.domLoaded = true;
4419 				callback(event);
4420 			}
4421 		}
4422 
4423 		// Use W3C method
4424 		if (w3cEventModel) {
4425 			addEvent(win, 'DOMContentLoaded', readyHandler);
4426 		} else {
4427 			// Use IE method
4428 			addEvent(doc, "readystatechange", function() {
4429 				if (doc.readyState === "complete") {
4430 					removeEvent(doc, "readystatechange", arguments.callee);
4431 					readyHandler();
4432 				}
4433 			});
4434 
4435 			// Wait until we can scroll, when we can the DOM is initialized
4436 			if (doc.documentElement.doScroll && win === win.top) {
4437 				(function() {
4438 					try {
4439 						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4440 						// http://javascript.nwbox.com/IEContentLoaded/
4441 						doc.documentElement.doScroll("left");
4442 					} catch (ex) {
4443 						setTimeout(arguments.callee, 0);
4444 						return;
4445 					}
4446 
4447 					readyHandler();
4448 				})();
4449 			}
4450 		}
4451 
4452 		// Fallback if any of the above methods should fail for some odd reason
4453 		addEvent(win, 'load', readyHandler);
4454 	}
4455 
4456 	function EventUtils(proxy) {
4457 		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4458 
4459 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4460 		hasFocusIn = "onfocusin" in document.documentElement;
4461 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4462 		count = 1;
4463 
4464 		// State if the DOMContentLoaded was executed or not
4465 		self.domLoaded = false;
4466 		self.events = events;
4467 
4468 		function executeHandlers(evt, id) {
4469 			var callbackList, i, l, callback;
4470 
4471 			callbackList = events[id][evt.type];
4472 			if (callbackList) {
4473 				for (i = 0, l = callbackList.length; i < l; i++) {
4474 					callback = callbackList[i];
4475 					
4476 					// Check if callback exists might be removed if a unbind is called inside the callback
4477 					if (callback && callback.func.call(callback.scope, evt) === false) {
4478 						evt.preventDefault();
4479 					}
4480 
4481 					// Should we stop propagation to immediate listeners
4482 					if (evt.isImmediatePropagationStopped()) {
4483 						return;
4484 					}
4485 				}
4486 			}
4487 		}
4488 
4489 		self.bind = function(target, names, callback, scope) {
4490 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4491 
4492 			// Native event handler function patches the event and executes the callbacks for the expando
4493 			function defaultNativeHandler(evt) {
4494 				executeHandlers(fix(evt || win.event), id);
4495 			}
4496 
4497 			// Don't bind to text nodes or comments
4498 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4499 				return;
4500 			}
4501 
4502 			// Create or get events id for the target
4503 			if (!target[expando]) {
4504 				id = count++;
4505 				target[expando] = id;
4506 				events[id] = {};
4507 			} else {
4508 				id = target[expando];
4509 
4510 				if (!events[id]) {
4511 					events[id] = {};
4512 				}
4513 			}
4514 
4515 			// Setup the specified scope or use the target as a default
4516 			scope = scope || target;
4517 
4518 			// Split names and bind each event, enables you to bind multiple events with one call
4519 			names = names.split(' ');
4520 			i = names.length;
4521 			while (i--) {
4522 				name = names[i];
4523 				nativeHandler = defaultNativeHandler;
4524 				fakeName = capture = false;
4525 
4526 				// Use ready instead of DOMContentLoaded
4527 				if (name === "DOMContentLoaded") {
4528 					name = "ready";
4529 				}
4530 
4531 				// DOM is already ready
4532 				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4533 					self.domLoaded = true;
4534 					callback.call(scope, fix({type: name}));
4535 					continue;
4536 				}
4537 
4538 				// Handle mouseenter/mouseleaver
4539 				if (!hasMouseEnterLeave) {
4540 					fakeName = mouseEnterLeave[name];
4541 
4542 					if (fakeName) {
4543 						nativeHandler = function(evt) {
4544 							var current, related;
4545 
4546 							current = evt.currentTarget;
4547 							related = evt.relatedTarget;
4548 
4549 							// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4550 							if (related && current.contains) {
4551 								// Use contains for performance
4552 								related = current.contains(related);
4553 							} else {
4554 								while (related && related !== current) {
4555 									related = related.parentNode;
4556 								}
4557 							}
4558 
4559 							// Fire fake event
4560 							if (!related) {
4561 								evt = fix(evt || win.event);
4562 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4563 								evt.target = current;
4564 								executeHandlers(evt, id);
4565 							}
4566 						};
4567 					}
4568 				}
4569 
4570 				// Fake bubbeling of focusin/focusout
4571 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4572 					capture = true;
4573 					fakeName = name === "focusin" ? "focus" : "blur";
4574 					nativeHandler = function(evt) {
4575 						evt = fix(evt || win.event);
4576 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4577 						executeHandlers(evt, id);
4578 					};
4579 				}
4580 
4581 				// Setup callback list and bind native event
4582 				callbackList = events[id][name];
4583 				if (!callbackList) {
4584 					events[id][name] = callbackList = [{func: callback, scope: scope}];
4585 					callbackList.fakeName = fakeName;
4586 					callbackList.capture = capture;
4587 
4588 					// Add the nativeHandler to the callback list so that we can later unbind it
4589 					callbackList.nativeHandler = nativeHandler;
4590 					if (!w3cEventModel) {
4591 						callbackList.proxyHandler = proxy(id);
4592 					}
4593 
4594 					// Check if the target has native events support
4595 					if (name === "ready") {
4596 						bindOnReady(target, nativeHandler, self);
4597 					} else {
4598 						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4599 					}
4600 				} else {
4601 					// If it already has an native handler then just push the callback
4602 					callbackList.push({func: callback, scope: scope});
4603 				}
4604 			}
4605 
4606 			target = callbackList = 0; // Clean memory for IE
4607 
4608 			return callback;
4609 		};
4610 
4611 		self.unbind = function(target, names, callback) {
4612 			var id, callbackList, i, ci, name, eventMap;
4613 
4614 			// Don't bind to text nodes or comments
4615 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4616 				return self;
4617 			}
4618 
4619 			// Unbind event or events if the target has the expando
4620 			id = target[expando];
4621 			if (id) {
4622 				eventMap = events[id];
4623 
4624 				// Specific callback
4625 				if (names) {
4626 					names = names.split(' ');
4627 					i = names.length;
4628 					while (i--) {
4629 						name = names[i];
4630 						callbackList = eventMap[name];
4631 
4632 						// Unbind the event if it exists in the map
4633 						if (callbackList) {
4634 							// Remove specified callback
4635 							if (callback) {
4636 								ci = callbackList.length;
4637 								while (ci--) {
4638 									if (callbackList[ci].func === callback) {
4639 										callbackList.splice(ci, 1);
4640 									}
4641 								}
4642 							}
4643 
4644 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4645 							if (!callback || callbackList.length === 0) {
4646 								delete eventMap[name];
4647 								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4648 							}
4649 						}
4650 					}
4651 				} else {
4652 					// All events for a specific element
4653 					for (name in eventMap) {
4654 						callbackList = eventMap[name];
4655 						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4656 					}
4657 
4658 					eventMap = {};
4659 				}
4660 
4661 				// Check if object is empty, if it isn't then we won't remove the expando map
4662 				for (name in eventMap) {
4663 					return self;
4664 				}
4665 
4666 				// Delete event object
4667 				delete events[id];
4668 
4669 				// Remove expando from target
4670 				try {
4671 					// IE will fail here since it can't delete properties from window
4672 					delete target[expando];
4673 				} catch (ex) {
4674 					// IE will set it to null
4675 					target[expando] = null;
4676 				}
4677 			}
4678 
4679 			return self;
4680 		};
4681 
4682 		self.fire = function(target, name, args) {
4683 			var id, event;
4684 
4685 			// Don't bind to text nodes or comments
4686 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4687 				return self;
4688 			}
4689 
4690 			// Build event object by patching the args
4691 			event = fix(null, args);
4692 			event.type = name;
4693 
4694 			do {
4695 				// Found an expando that means there is listeners to execute
4696 				id = target[expando];
4697 				if (id) {
4698 					executeHandlers(event, id);
4699 				}
4700 
4701 				// Walk up the DOM
4702 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4703 			} while (target && !event.isPropagationStopped());
4704 
4705 			return self;
4706 		};
4707 
4708 		self.clean = function(target) {
4709 			var i, children, unbind = self.unbind;
4710 	
4711 			// Don't bind to text nodes or comments
4712 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4713 				return self;
4714 			}
4715 
4716 			// Unbind any element on the specificed target
4717 			if (target[expando]) {
4718 				unbind(target);
4719 			}
4720 
4721 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4722 			if (!target.getElementsByTagName) {
4723 				target = target.document;
4724 			}
4725 
4726 			// Remove events from each child element
4727 			if (target && target.getElementsByTagName) {
4728 				unbind(target);
4729 
4730 				children = target.getElementsByTagName('*');
4731 				i = children.length;
4732 				while (i--) {
4733 					target = children[i];
4734 
4735 					if (target[expando]) {
4736 						unbind(target);
4737 					}
4738 				}
4739 			}
4740 
4741 			return self;
4742 		};
4743 
4744 		self.callNativeHandler = function(id, evt) {
4745 			if (events) {
4746 				events[id][evt.type].nativeHandler(evt);
4747 			}
4748 		};
4749 
4750 		self.destory = function() {
4751 			events = {};
4752 		};
4753 
4754 		// Legacy function calls
4755 
4756 		self.add = function(target, events, func, scope) {
4757 			// Old API supported direct ID assignment
4758 			if (typeof(target) === "string") {
4759 				target = document.getElementById(target);
4760 			}
4761 
4762 			// Old API supported multiple targets
4763 			if (target && target instanceof Array) {
4764 				var i = target.length;
4765 
4766 				while (i--) {
4767 					self.add(target[i], events, func, scope);
4768 				}
4769 
4770 				return;
4771 			}
4772 
4773 			// Old API called ready init
4774 			if (events === "init") {
4775 				events = "ready";
4776 			}
4777 
4778 			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
4779 		};
4780 
4781 		self.remove = function(target, events, func, scope) {
4782 			if (!target) {
4783 				return self;
4784 			}
4785 
4786 			// Old API supported direct ID assignment
4787 			if (typeof(target) === "string") {
4788 				target = document.getElementById(target);
4789 			}
4790 
4791 			// Old API supported multiple targets
4792 			if (target instanceof Array) {
4793 				var i = target.length;
4794 
4795 				while (i--) {
4796 					self.remove(target[i], events, func, scope);
4797 				}
4798 
4799 				return self;
4800 			}
4801 
4802 			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
4803 		};
4804 
4805 		self.clear = function(target) {
4806 			// Old API supported direct ID assignment
4807 			if (typeof(target) === "string") {
4808 				target = document.getElementById(target);
4809 			}
4810 
4811 			return self.clean(target);
4812 		};
4813 
4814 		self.cancel = function(e) {
4815 			if (e) {
4816 				self.prevent(e);
4817 				self.stop(e);
4818 			}
4819 
4820 			return false;
4821 		};
4822 
4823 		self.prevent = function(e) {
4824 			if (!e.preventDefault) {
4825 				e = fix(e);
4826 			}
4827 
4828 			e.preventDefault();
4829 
4830 			return false;
4831 		};
4832 
4833 		self.stop = function(e) {
4834 			if (!e.stopPropagation) {
4835 				e = fix(e);
4836 			}
4837 
4838 			e.stopPropagation();
4839 
4840 			return false;
4841 		};
4842 	}
4843 
4844 	namespace.EventUtils = EventUtils;
4845 
4846 	namespace.Event = new EventUtils(function(id) {
4847 		return function(evt) {
4848 			tinymce.dom.Event.callNativeHandler(id, evt);
4849 		};
4850 	});
4851 
4852 	// Bind ready event when tinymce script is loaded
4853 	namespace.Event.bind(window, 'ready', function() {});
4854 
4855 	namespace = 0;
4856 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
4857 
4858 tinymce.dom.TreeWalker = function(start_node, root_node) {
4859 	var node = start_node;
4860 
4861 	function findSibling(node, start_name, sibling_name, shallow) {
4862 		var sibling, parent;
4863 
4864 		if (node) {
4865 			// Walk into nodes if it has a start
4866 			if (!shallow && node[start_name])
4867 				return node[start_name];
4868 
4869 			// Return the sibling if it has one
4870 			if (node != root_node) {
4871 				sibling = node[sibling_name];
4872 				if (sibling)
4873 					return sibling;
4874 
4875 				// Walk up the parents to look for siblings
4876 				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
4877 					sibling = parent[sibling_name];
4878 					if (sibling)
4879 						return sibling;
4880 				}
4881 			}
4882 		}
4883 	};
4884 
4885 	this.current = function() {
4886 		return node;
4887 	};
4888 
4889 	this.next = function(shallow) {
4890 		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
4891 	};
4892 
4893 	this.prev = function(shallow) {
4894 		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
4895 	};
4896 };
4897 
4898 (function(tinymce) {
4899 	// Shorten names
4900 	var each = tinymce.each,
4901 		is = tinymce.is,
4902 		isWebKit = tinymce.isWebKit,
4903 		isIE = tinymce.isIE,
4904 		Entities = tinymce.html.Entities,
4905 		simpleSelectorRe = /^([a-z0-9],?)+$/i,
4906 		whiteSpaceRegExp = /^[ \t\r\n]*$/;
4907 
4908 	tinymce.create('tinymce.dom.DOMUtils', {
4909 		doc : null,
4910 		root : null,
4911 		files : null,
4912 		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
4913 		props : {
4914 			"for" : "htmlFor",
4915 			"class" : "className",
4916 			className : "className",
4917 			checked : "checked",
4918 			disabled : "disabled",
4919 			maxlength : "maxLength",
4920 			readonly : "readOnly",
4921 			selected : "selected",
4922 			value : "value",
4923 			id : "id",
4924 			name : "name",
4925 			type : "type"
4926 		},
4927 
4928 		DOMUtils : function(d, s) {
4929 			var t = this, globalStyle, name, blockElementsMap;
4930 
4931 			t.doc = d;
4932 			t.win = window;
4933 			t.files = {};
4934 			t.cssFlicker = false;
4935 			t.counter = 0;
4936 			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
4937 			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
4938 			t.hasOuterHTML = "outerHTML" in d.createElement("a");
4939 
4940 			t.settings = s = tinymce.extend({
4941 				keep_values : false,
4942 				hex_colors : 1
4943 			}, s);
4944 			
4945 			t.schema = s.schema;
4946 			t.styles = new tinymce.html.Styles({
4947 				url_converter : s.url_converter,
4948 				url_converter_scope : s.url_converter_scope
4949 			}, s.schema);
4950 
4951 			// Fix IE6SP2 flicker and check it failed for pre SP2
4952 			if (tinymce.isIE6) {
4953 				try {
4954 					d.execCommand('BackgroundImageCache', false, true);
4955 				} catch (e) {
4956 					t.cssFlicker = true;
4957 				}
4958 			}
4959 
4960 			t.fixDoc(d);
4961 			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
4962 			tinymce.addUnload(t.destroy, t);
4963 			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
4964 
4965 			t.isBlock = function(node) {
4966 				// This function is called in module pattern style since it might be executed with the wrong this scope
4967 				var type = node.nodeType;
4968 
4969 				// If it's a node then check the type and use the nodeName
4970 				if (type)
4971 					return !!(type === 1 && blockElementsMap[node.nodeName]);
4972 
4973 				return !!blockElementsMap[node];
4974 			};
4975 		},
4976 
4977 		fixDoc: function(doc) {
4978 			var settings = this.settings, name;
4979 
4980 			if (isIE && settings.schema) {
4981 				// Add missing HTML 4/5 elements to IE
4982 				('abbr article aside audio canvas ' +
4983 				'details figcaption figure footer ' +
4984 				'header hgroup mark menu meter nav ' +
4985 				'output progress section summary ' +
4986 				'time video').replace(/\w+/g, function(name) {
4987 					doc.createElement(name);
4988 				});
4989 
4990 				// Create all custom elements
4991 				for (name in settings.schema.getCustomElements()) {
4992 					doc.createElement(name);
4993 				}
4994 			}
4995 		},
4996 
4997 		clone: function(node, deep) {
4998 			var self = this, clone, doc;
4999 
5000 			// TODO: Add feature detection here in the future
5001 			if (!isIE || node.nodeType !== 1 || deep) {
5002 				return node.cloneNode(deep);
5003 			}
5004 
5005 			doc = self.doc;
5006 
5007 			// Make a HTML5 safe shallow copy
5008 			if (!deep) {
5009 				clone = doc.createElement(node.nodeName);
5010 
5011 				// Copy attribs
5012 				each(self.getAttribs(node), function(attr) {
5013 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5014 				});
5015 
5016 				return clone;
5017 			}
5018 /*
5019 			// Setup HTML5 patched document fragment
5020 			if (!self.frag) {
5021 				self.frag = doc.createDocumentFragment();
5022 				self.fixDoc(self.frag);
5023 			}
5024 
5025 			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5026 			clone = doc.createElement('div');
5027 			self.frag.appendChild(clone);
5028 			clone.innerHTML = node.outerHTML;
5029 			self.frag.removeChild(clone);
5030 */
5031 			return clone.firstChild;
5032 		},
5033 
5034 		getRoot : function() {
5035 			var t = this, s = t.settings;
5036 
5037 			return (s && t.get(s.root_element)) || t.doc.body;
5038 		},
5039 
5040 		getViewPort : function(w) {
5041 			var d, b;
5042 
5043 			w = !w ? this.win : w;
5044 			d = w.document;
5045 			b = this.boxModel ? d.documentElement : d.body;
5046 
5047 			// Returns viewport size excluding scrollbars
5048 			return {
5049 				x : w.pageXOffset || b.scrollLeft,
5050 				y : w.pageYOffset || b.scrollTop,
5051 				w : w.innerWidth || b.clientWidth,
5052 				h : w.innerHeight || b.clientHeight
5053 			};
5054 		},
5055 
5056 		getRect : function(e) {
5057 			var p, t = this, sr;
5058 
5059 			e = t.get(e);
5060 			p = t.getPos(e);
5061 			sr = t.getSize(e);
5062 
5063 			return {
5064 				x : p.x,
5065 				y : p.y,
5066 				w : sr.w,
5067 				h : sr.h
5068 			};
5069 		},
5070 
5071 		getSize : function(e) {
5072 			var t = this, w, h;
5073 
5074 			e = t.get(e);
5075 			w = t.getStyle(e, 'width');
5076 			h = t.getStyle(e, 'height');
5077 
5078 			// Non pixel value, then force offset/clientWidth
5079 			if (w.indexOf('px') === -1)
5080 				w = 0;
5081 
5082 			// Non pixel value, then force offset/clientWidth
5083 			if (h.indexOf('px') === -1)
5084 				h = 0;
5085 
5086 			return {
5087 				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5088 				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5089 			};
5090 		},
5091 
5092 		getParent : function(n, f, r) {
5093 			return this.getParents(n, f, r, false);
5094 		},
5095 
5096 		getParents : function(n, f, r, c) {
5097 			var t = this, na, se = t.settings, o = [];
5098 
5099 			n = t.get(n);
5100 			c = c === undefined;
5101 
5102 			if (se.strict_root)
5103 				r = r || t.getRoot();
5104 
5105 			// Wrap node name as func
5106 			if (is(f, 'string')) {
5107 				na = f;
5108 
5109 				if (f === '*') {
5110 					f = function(n) {return n.nodeType == 1;};
5111 				} else {
5112 					f = function(n) {
5113 						return t.is(n, na);
5114 					};
5115 				}
5116 			}
5117 
5118 			while (n) {
5119 				if (n == r || !n.nodeType || n.nodeType === 9)
5120 					break;
5121 
5122 				if (!f || f(n)) {
5123 					if (c)
5124 						o.push(n);
5125 					else
5126 						return n;
5127 				}
5128 
5129 				n = n.parentNode;
5130 			}
5131 
5132 			return c ? o : null;
5133 		},
5134 
5135 		get : function(e) {
5136 			var n;
5137 
5138 			if (e && this.doc && typeof(e) == 'string') {
5139 				n = e;
5140 				e = this.doc.getElementById(e);
5141 
5142 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5143 				if (e && e.id !== n)
5144 					return this.doc.getElementsByName(n)[1];
5145 			}
5146 
5147 			return e;
5148 		},
5149 
5150 		getNext : function(node, selector) {
5151 			return this._findSib(node, selector, 'nextSibling');
5152 		},
5153 
5154 		getPrev : function(node, selector) {
5155 			return this._findSib(node, selector, 'previousSibling');
5156 		},
5157 
5158 
5159 		select : function(pa, s) {
5160 			var t = this;
5161 
5162 			return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
5163 		},
5164 
5165 		is : function(n, selector) {
5166 			var i;
5167 
5168 			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5169 			if (n.length === undefined) {
5170 				// Simple all selector
5171 				if (selector === '*')
5172 					return n.nodeType == 1;
5173 
5174 				// Simple selector just elements
5175 				if (simpleSelectorRe.test(selector)) {
5176 					selector = selector.toLowerCase().split(/,/);
5177 					n = n.nodeName.toLowerCase();
5178 
5179 					for (i = selector.length - 1; i >= 0; i--) {
5180 						if (selector[i] == n)
5181 							return true;
5182 					}
5183 
5184 					return false;
5185 				}
5186 			}
5187 
5188 			return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
5189 		},
5190 
5191 
5192 		add : function(p, n, a, h, c) {
5193 			var t = this;
5194 
5195 			return this.run(p, function(p) {
5196 				var e, k;
5197 
5198 				e = is(n, 'string') ? t.doc.createElement(n) : n;
5199 				t.setAttribs(e, a);
5200 
5201 				if (h) {
5202 					if (h.nodeType)
5203 						e.appendChild(h);
5204 					else
5205 						t.setHTML(e, h);
5206 				}
5207 
5208 				return !c ? p.appendChild(e) : e;
5209 			});
5210 		},
5211 
5212 		create : function(n, a, h) {
5213 			return this.add(this.doc.createElement(n), n, a, h, 1);
5214 		},
5215 
5216 		createHTML : function(n, a, h) {
5217 			var o = '', t = this, k;
5218 
5219 			o += '<' + n;
5220 
5221 			for (k in a) {
5222 				if (a.hasOwnProperty(k))
5223 					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5224 			}
5225 
5226 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5227 			if (typeof(h) != "undefined")
5228 				return o + '>' + h + '</' + n + '>';
5229 
5230 			return o + ' />';
5231 		},
5232 
5233 		remove : function(node, keep_children) {
5234 			return this.run(node, function(node) {
5235 				var child, parent = node.parentNode;
5236 
5237 				if (!parent)
5238 					return null;
5239 
5240 				if (keep_children) {
5241 					while (child = node.firstChild) {
5242 						// IE 8 will crash if you don't remove completely empty text nodes
5243 						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5244 							parent.insertBefore(child, node);
5245 						else
5246 							node.removeChild(child);
5247 					}
5248 				}
5249 
5250 				return parent.removeChild(node);
5251 			});
5252 		},
5253 
5254 		setStyle : function(n, na, v) {
5255 			var t = this;
5256 
5257 			return t.run(n, function(e) {
5258 				var s, i;
5259 
5260 				s = e.style;
5261 
5262 				// Camelcase it, if needed
5263 				na = na.replace(/-(\D)/g, function(a, b){
5264 					return b.toUpperCase();
5265 				});
5266 
5267 				// Default px suffix on these
5268 				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5269 					v += 'px';
5270 
5271 				switch (na) {
5272 					case 'opacity':
5273 						// IE specific opacity
5274 						if (isIE) {
5275 							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5276 
5277 							if (!n.currentStyle || !n.currentStyle.hasLayout)
5278 								s.display = 'inline-block';
5279 						}
5280 
5281 						// Fix for older browsers
5282 						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5283 						break;
5284 
5285 					case 'float':
5286 						isIE ? s.styleFloat = v : s.cssFloat = v;
5287 						break;
5288 					
5289 					default:
5290 						s[na] = v || '';
5291 				}
5292 
5293 				// Force update of the style data
5294 				if (t.settings.update_styles)
5295 					t.setAttrib(e, 'data-mce-style');
5296 			});
5297 		},
5298 
5299 		getStyle : function(n, na, c) {
5300 			n = this.get(n);
5301 
5302 			if (!n)
5303 				return;
5304 
5305 			// Gecko
5306 			if (this.doc.defaultView && c) {
5307 				// Remove camelcase
5308 				na = na.replace(/[A-Z]/g, function(a){
5309 					return '-' + a;
5310 				});
5311 
5312 				try {
5313 					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5314 				} catch (ex) {
5315 					// Old safari might fail
5316 					return null;
5317 				}
5318 			}
5319 
5320 			// Camelcase it, if needed
5321 			na = na.replace(/-(\D)/g, function(a, b){
5322 				return b.toUpperCase();
5323 			});
5324 
5325 			if (na == 'float')
5326 				na = isIE ? 'styleFloat' : 'cssFloat';
5327 
5328 			// IE & Opera
5329 			if (n.currentStyle && c)
5330 				return n.currentStyle[na];
5331 
5332 			return n.style ? n.style[na] : undefined;
5333 		},
5334 
5335 		setStyles : function(e, o) {
5336 			var t = this, s = t.settings, ol;
5337 
5338 			ol = s.update_styles;
5339 			s.update_styles = 0;
5340 
5341 			each(o, function(v, n) {
5342 				t.setStyle(e, n, v);
5343 			});
5344 
5345 			// Update style info
5346 			s.update_styles = ol;
5347 			if (s.update_styles)
5348 				t.setAttrib(e, s.cssText);
5349 		},
5350 
5351 		removeAllAttribs: function(e) {
5352 			return this.run(e, function(e) {
5353 				var i, attrs = e.attributes;
5354 				for (i = attrs.length - 1; i >= 0; i--) {
5355 					e.removeAttributeNode(attrs.item(i));
5356 				}
5357 			});
5358 		},
5359 
5360 		setAttrib : function(e, n, v) {
5361 			var t = this;
5362 
5363 			// Whats the point
5364 			if (!e || !n)
5365 				return;
5366 
5367 			// Strict XML mode
5368 			if (t.settings.strict)
5369 				n = n.toLowerCase();
5370 
5371 			return this.run(e, function(e) {
5372 				var s = t.settings;
5373 				var originalValue = e.getAttribute(n);
5374 				if (v !== null) {
5375 					switch (n) {
5376 						case "style":
5377 							if (!is(v, 'string')) {
5378 								each(v, function(v, n) {
5379 									t.setStyle(e, n, v);
5380 								});
5381 
5382 								return;
5383 							}
5384 
5385 							// No mce_style for elements with these since they might get resized by the user
5386 							if (s.keep_values) {
5387 								if (v && !t._isRes(v))
5388 									e.setAttribute('data-mce-style', v, 2);
5389 								else
5390 									e.removeAttribute('data-mce-style', 2);
5391 							}
5392 
5393 							e.style.cssText = v;
5394 							break;
5395 
5396 						case "class":
5397 							e.className = v || ''; // Fix IE null bug
5398 							break;
5399 
5400 						case "src":
5401 						case "href":
5402 							if (s.keep_values) {
5403 								if (s.url_converter)
5404 									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5405 
5406 								t.setAttrib(e, 'data-mce-' + n, v, 2);
5407 							}
5408 
5409 							break;
5410 
5411 						case "shape":
5412 							e.setAttribute('data-mce-style', v);
5413 							break;
5414 					}
5415 				}
5416 				if (is(v) && v !== null && v.length !== 0)
5417 					e.setAttribute(n, '' + v, 2);
5418 				else
5419 					e.removeAttribute(n, 2);
5420 
5421 				// fire onChangeAttrib event for attributes that have changed
5422 				if (tinyMCE.activeEditor && originalValue != v) {
5423 					var ed = tinyMCE.activeEditor;
5424 					ed.onSetAttrib.dispatch(ed, e, n, v);
5425 				}
5426 			});
5427 		},
5428 
5429 		setAttribs : function(e, o) {
5430 			var t = this;
5431 
5432 			return this.run(e, function(e) {
5433 				each(o, function(v, n) {
5434 					t.setAttrib(e, n, v);
5435 				});
5436 			});
5437 		},
5438 
5439 		getAttrib : function(e, n, dv) {
5440 			var v, t = this, undef;
5441 
5442 			e = t.get(e);
5443 
5444 			if (!e || e.nodeType !== 1)
5445 				return dv === undef ? false : dv;
5446 
5447 			if (!is(dv))
5448 				dv = '';
5449 
5450 			// Try the mce variant for these
5451 			if (/^(src|href|style|coords|shape)$/.test(n)) {
5452 				v = e.getAttribute("data-mce-" + n);
5453 
5454 				if (v)
5455 					return v;
5456 			}
5457 
5458 			if (isIE && t.props[n]) {
5459 				v = e[t.props[n]];
5460 				v = v && v.nodeValue ? v.nodeValue : v;
5461 			}
5462 
5463 			if (!v)
5464 				v = e.getAttribute(n, 2);
5465 
5466 			// Check boolean attribs
5467 			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5468 				if (e[t.props[n]] === true && v === '')
5469 					return n;
5470 
5471 				return v ? n : '';
5472 			}
5473 
5474 			// Inner input elements will override attributes on form elements
5475 			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5476 				return e.getAttributeNode(n).nodeValue;
5477 
5478 			if (n === 'style') {
5479 				v = v || e.style.cssText;
5480 
5481 				if (v) {
5482 					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5483 
5484 					if (t.settings.keep_values && !t._isRes(v))
5485 						e.setAttribute('data-mce-style', v);
5486 				}
5487 			}
5488 
5489 			// Remove Apple and WebKit stuff
5490 			if (isWebKit && n === "class" && v)
5491 				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5492 
5493 			// Handle IE issues
5494 			if (isIE) {
5495 				switch (n) {
5496 					case 'rowspan':
5497 					case 'colspan':
5498 						// IE returns 1 as default value
5499 						if (v === 1)
5500 							v = '';
5501 
5502 						break;
5503 
5504 					case 'size':
5505 						// IE returns +0 as default value for size
5506 						if (v === '+0' || v === 20 || v === 0)
5507 							v = '';
5508 
5509 						break;
5510 
5511 					case 'width':
5512 					case 'height':
5513 					case 'vspace':
5514 					case 'checked':
5515 					case 'disabled':
5516 					case 'readonly':
5517 						if (v === 0)
5518 							v = '';
5519 
5520 						break;
5521 
5522 					case 'hspace':
5523 						// IE returns -1 as default value
5524 						if (v === -1)
5525 							v = '';
5526 
5527 						break;
5528 
5529 					case 'maxlength':
5530 					case 'tabindex':
5531 						// IE returns default value
5532 						if (v === 32768 || v === 2147483647 || v === '32768')
5533 							v = '';
5534 
5535 						break;
5536 
5537 					case 'multiple':
5538 					case 'compact':
5539 					case 'noshade':
5540 					case 'nowrap':
5541 						if (v === 65535)
5542 							return n;
5543 
5544 						return dv;
5545 
5546 					case 'shape':
5547 						v = v.toLowerCase();
5548 						break;
5549 
5550 					default:
5551 						// IE has odd anonymous function for event attributes
5552 						if (n.indexOf('on') === 0 && v)
5553 							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5554 				}
5555 			}
5556 
5557 			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5558 		},
5559 
5560 		getPos : function(n, ro) {
5561 			var t = this, x = 0, y = 0, e, d = t.doc, r;
5562 
5563 			n = t.get(n);
5564 			ro = ro || d.body;
5565 
5566 			if (n) {
5567 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5568 				if (n.getBoundingClientRect) {
5569 					n = n.getBoundingClientRect();
5570 					e = t.boxModel ? d.documentElement : d.body;
5571 
5572 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5573 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5574 					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5575 					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5576 
5577 					return {x : x, y : y};
5578 				}
5579 
5580 				r = n;
5581 				while (r && r != ro && r.nodeType) {
5582 					x += r.offsetLeft || 0;
5583 					y += r.offsetTop || 0;
5584 					r = r.offsetParent;
5585 				}
5586 
5587 				r = n.parentNode;
5588 				while (r && r != ro && r.nodeType) {
5589 					x -= r.scrollLeft || 0;
5590 					y -= r.scrollTop || 0;
5591 					r = r.parentNode;
5592 				}
5593 			}
5594 
5595 			return {x : x, y : y};
5596 		},
5597 
5598 		parseStyle : function(st) {
5599 			return this.styles.parse(st);
5600 		},
5601 
5602 		serializeStyle : function(o, name) {
5603 			return this.styles.serialize(o, name);
5604 		},
5605 
5606 		addStyle: function(cssText) {
5607 			var doc = this.doc, head;
5608 
5609 			// Create style element if needed
5610 			styleElm = doc.getElementById('mceDefaultStyles');
5611 			if (!styleElm) {
5612 				styleElm = doc.createElement('style'),
5613 				styleElm.id = 'mceDefaultStyles';
5614 				styleElm.type = 'text/css';
5615 
5616 				head = doc.getElementsByTagName('head')[0]
5617 				if (head.firstChild) {
5618 					head.insertBefore(styleElm, head.firstChild);
5619 				} else {
5620 					head.appendChild(styleElm);
5621 				}
5622 			}
5623 
5624 			// Append style data to old or new style element
5625 			if (styleElm.styleSheet) {
5626 				styleElm.styleSheet.cssText += cssText;
5627 			} else {
5628 				styleElm.appendChild(doc.createTextNode(cssText));
5629 			}
5630 		},
5631 
5632 		loadCSS : function(u) {
5633 			var t = this, d = t.doc, head;
5634 
5635 			if (!u)
5636 				u = '';
5637 
5638 			head = d.getElementsByTagName('head')[0];
5639 
5640 			each(u.split(','), function(u) {
5641 				var link;
5642 
5643 				if (t.files[u])
5644 					return;
5645 
5646 				t.files[u] = true;
5647 				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5648 
5649 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5650 				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5651 				// It's ugly but it seems to work fine.
5652 				if (isIE && d.documentMode && d.recalc) {
5653 					link.onload = function() {
5654 						if (d.recalc)
5655 							d.recalc();
5656 
5657 						link.onload = null;
5658 					};
5659 				}
5660 
5661 				head.appendChild(link);
5662 			});
5663 		},
5664 
5665 		addClass : function(e, c) {
5666 			return this.run(e, function(e) {
5667 				var o;
5668 
5669 				if (!c)
5670 					return 0;
5671 
5672 				if (this.hasClass(e, c))
5673 					return e.className;
5674 
5675 				o = this.removeClass(e, c);
5676 
5677 				return e.className = (o != '' ? (o + ' ') : '') + c;
5678 			});
5679 		},
5680 
5681 		removeClass : function(e, c) {
5682 			var t = this, re;
5683 
5684 			return t.run(e, function(e) {
5685 				var v;
5686 
5687 				if (t.hasClass(e, c)) {
5688 					if (!re)
5689 						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5690 
5691 					v = e.className.replace(re, ' ');
5692 					v = tinymce.trim(v != ' ' ? v : '');
5693 
5694 					e.className = v;
5695 
5696 					// Empty class attr
5697 					if (!v) {
5698 						e.removeAttribute('class');
5699 						e.removeAttribute('className');
5700 					}
5701 
5702 					return v;
5703 				}
5704 
5705 				return e.className;
5706 			});
5707 		},
5708 
5709 		hasClass : function(n, c) {
5710 			n = this.get(n);
5711 
5712 			if (!n || !c)
5713 				return false;
5714 
5715 			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5716 		},
5717 
5718 		show : function(e) {
5719 			return this.setStyle(e, 'display', 'block');
5720 		},
5721 
5722 		hide : function(e) {
5723 			return this.setStyle(e, 'display', 'none');
5724 		},
5725 
5726 		isHidden : function(e) {
5727 			e = this.get(e);
5728 
5729 			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5730 		},
5731 
5732 		uniqueId : function(p) {
5733 			return (!p ? 'mce_' : p) + (this.counter++);
5734 		},
5735 
5736 		setHTML : function(element, html) {
5737 			var self = this;
5738 
5739 			return self.run(element, function(element) {
5740 				if (isIE) {
5741 					// Remove all child nodes, IE keeps empty text nodes in DOM
5742 					while (element.firstChild)
5743 						element.removeChild(element.firstChild);
5744 
5745 					try {
5746 						// IE will remove comments from the beginning
5747 						// unless you padd the contents with something
5748 						element.innerHTML = '<br />' + html;
5749 						element.removeChild(element.firstChild);
5750 					} catch (ex) {
5751 						// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
5752 						// This seems to fix this problem
5753 
5754 						// Create new div with HTML contents and a BR infront to keep comments
5755 						var newElement = self.create('div');
5756 						newElement.innerHTML = '<br />' + html;
5757 
5758 						// Add all children from div to target
5759 						each (tinymce.grep(newElement.childNodes), function(node, i) {
5760 							// Skip br element
5761 							if (i && element.canHaveHTML)
5762 								element.appendChild(node);
5763 						});
5764 					}
5765 				} else
5766 					element.innerHTML = html;
5767 
5768 				return html;
5769 			});
5770 		},
5771 
5772 		getOuterHTML : function(elm) {
5773 			var doc, self = this;
5774 
5775 			elm = self.get(elm);
5776 
5777 			if (!elm)
5778 				return null;
5779 
5780 			if (elm.nodeType === 1 && self.hasOuterHTML)
5781 				return elm.outerHTML;
5782 
5783 			doc = (elm.ownerDocument || self.doc).createElement("body");
5784 			doc.appendChild(elm.cloneNode(true));
5785 
5786 			return doc.innerHTML;
5787 		},
5788 
5789 		setOuterHTML : function(e, h, d) {
5790 			var t = this;
5791 
5792 			function setHTML(e, h, d) {
5793 				var n, tp;
5794 
5795 				tp = d.createElement("body");
5796 				tp.innerHTML = h;
5797 
5798 				n = tp.lastChild;
5799 				while (n) {
5800 					t.insertAfter(n.cloneNode(true), e);
5801 					n = n.previousSibling;
5802 				}
5803 
5804 				t.remove(e);
5805 			};
5806 
5807 			return this.run(e, function(e) {
5808 				e = t.get(e);
5809 
5810 				// Only set HTML on elements
5811 				if (e.nodeType == 1) {
5812 					d = d || e.ownerDocument || t.doc;
5813 
5814 					if (isIE) {
5815 						try {
5816 							// Try outerHTML for IE it sometimes produces an unknown runtime error
5817 							if (isIE && e.nodeType == 1)
5818 								e.outerHTML = h;
5819 							else
5820 								setHTML(e, h, d);
5821 						} catch (ex) {
5822 							// Fix for unknown runtime error
5823 							setHTML(e, h, d);
5824 						}
5825 					} else
5826 						setHTML(e, h, d);
5827 				}
5828 			});
5829 		},
5830 
5831 		decode : Entities.decode,
5832 
5833 		encode : Entities.encodeAllRaw,
5834 
5835 		insertAfter : function(node, reference_node) {
5836 			reference_node = this.get(reference_node);
5837 
5838 			return this.run(node, function(node) {
5839 				var parent, nextSibling;
5840 
5841 				parent = reference_node.parentNode;
5842 				nextSibling = reference_node.nextSibling;
5843 
5844 				if (nextSibling)
5845 					parent.insertBefore(node, nextSibling);
5846 				else
5847 					parent.appendChild(node);
5848 
5849 				return node;
5850 			});
5851 		},
5852 
5853 		replace : function(n, o, k) {
5854 			var t = this;
5855 
5856 			if (is(o, 'array'))
5857 				n = n.cloneNode(true);
5858 
5859 			return t.run(o, function(o) {
5860 				if (k) {
5861 					each(tinymce.grep(o.childNodes), function(c) {
5862 						n.appendChild(c);
5863 					});
5864 				}
5865 
5866 				return o.parentNode.replaceChild(n, o);
5867 			});
5868 		},
5869 
5870 		rename : function(elm, name) {
5871 			var t = this, newElm;
5872 
5873 			if (elm.nodeName != name.toUpperCase()) {
5874 				// Rename block element
5875 				newElm = t.create(name);
5876 
5877 				// Copy attribs to new block
5878 				each(t.getAttribs(elm), function(attr_node) {
5879 					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
5880 				});
5881 
5882 				// Replace block
5883 				t.replace(newElm, elm, 1);
5884 			}
5885 
5886 			return newElm || elm;
5887 		},
5888 
5889 		findCommonAncestor : function(a, b) {
5890 			var ps = a, pe;
5891 
5892 			while (ps) {
5893 				pe = b;
5894 
5895 				while (pe && ps != pe)
5896 					pe = pe.parentNode;
5897 
5898 				if (ps == pe)
5899 					break;
5900 
5901 				ps = ps.parentNode;
5902 			}
5903 
5904 			if (!ps && a.ownerDocument)
5905 				return a.ownerDocument.documentElement;
5906 
5907 			return ps;
5908 		},
5909 
5910 		toHex : function(s) {
5911 			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
5912 
5913 			function hex(s) {
5914 				s = parseInt(s, 10).toString(16);
5915 
5916 				return s.length > 1 ? s : '0' + s; // 0 -> 00
5917 			};
5918 
5919 			if (c) {
5920 				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
5921 
5922 				return s;
5923 			}
5924 
5925 			return s;
5926 		},
5927 
5928 		getClasses : function() {
5929 			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
5930 
5931 			if (t.classes)
5932 				return t.classes;
5933 
5934 			function addClasses(s) {
5935 				// IE style imports
5936 				each(s.imports, function(r) {
5937 					addClasses(r);
5938 				});
5939 
5940 				each(s.cssRules || s.rules, function(r) {
5941 					// Real type or fake it on IE
5942 					switch (r.type || 1) {
5943 						// Rule
5944 						case 1:
5945 							if (r.selectorText) {
5946 								each(r.selectorText.split(','), function(v) {
5947 									v = v.replace(/^\s*|\s*$|^\s\./g, "");
5948 
5949 									// Is internal or it doesn't contain a class
5950 									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
5951 										return;
5952 
5953 									// Remove everything but class name
5954 									ov = v;
5955 									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
5956 
5957 									// Filter classes
5958 									if (f && !(v = f(v, ov)))
5959 										return;
5960 
5961 									if (!lo[v]) {
5962 										cl.push({'class' : v});
5963 										lo[v] = 1;
5964 									}
5965 								});
5966 							}
5967 							break;
5968 
5969 						// Import
5970 						case 3:
5971 							addClasses(r.styleSheet);
5972 							break;
5973 					}
5974 				});
5975 			};
5976 
5977 			try {
5978 				each(t.doc.styleSheets, addClasses);
5979 			} catch (ex) {
5980 				// Ignore
5981 			}
5982 
5983 			if (cl.length > 0)
5984 				t.classes = cl;
5985 
5986 			return cl;
5987 		},
5988 
5989 		run : function(e, f, s) {
5990 			var t = this, o;
5991 
5992 			if (t.doc && typeof(e) === 'string')
5993 				e = t.get(e);
5994 
5995 			if (!e)
5996 				return false;
5997 
5998 			s = s || this;
5999 			if (!e.nodeType && (e.length || e.length === 0)) {
6000 				o = [];
6001 
6002 				each(e, function(e, i) {
6003 					if (e) {
6004 						if (typeof(e) == 'string')
6005 							e = t.doc.getElementById(e);
6006 
6007 						o.push(f.call(s, e, i));
6008 					}
6009 				});
6010 
6011 				return o;
6012 			}
6013 
6014 			return f.call(s, e);
6015 		},
6016 
6017 		getAttribs : function(n) {
6018 			var o;
6019 
6020 			n = this.get(n);
6021 
6022 			if (!n)
6023 				return [];
6024 
6025 			if (isIE) {
6026 				o = [];
6027 
6028 				// Object will throw exception in IE
6029 				if (n.nodeName == 'OBJECT')
6030 					return n.attributes;
6031 
6032 				// IE doesn't keep the selected attribute if you clone option elements
6033 				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6034 					o.push({specified : 1, nodeName : 'selected'});
6035 
6036 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6037 				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6038 					o.push({specified : 1, nodeName : a});
6039 				});
6040 
6041 				return o;
6042 			}
6043 
6044 			return n.attributes;
6045 		},
6046 
6047 		isEmpty : function(node, elements) {
6048 			var self = this, i, attributes, type, walker, name, brCount = 0;
6049 
6050 			node = node.firstChild;
6051 			if (node) {
6052 				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6053 				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6054 
6055 				do {
6056 					type = node.nodeType;
6057 
6058 					if (type === 1) {
6059 						// Ignore bogus elements
6060 						if (node.getAttribute('data-mce-bogus'))
6061 							continue;
6062 
6063 						// Keep empty elements like <img />
6064 						name = node.nodeName.toLowerCase();
6065 						if (elements && elements[name]) {
6066 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6067 							if (name === 'br') {
6068 								brCount++;
6069 								continue;
6070 							}
6071 
6072 							return false;
6073 						}
6074 
6075 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6076 						attributes = self.getAttribs(node);
6077 						i = node.attributes.length;
6078 						while (i--) {
6079 							name = node.attributes[i].nodeName;
6080 							if (name === "name" || name === 'data-mce-bookmark')
6081 								return false;
6082 						}
6083 					}
6084 
6085 					// Keep comment nodes
6086 					if (type == 8)
6087 						return false;
6088 
6089 					// Keep non whitespace text nodes
6090 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6091 						return false;
6092 				} while (node = walker.next());
6093 			}
6094 
6095 			return brCount <= 1;
6096 		},
6097 
6098 		destroy : function(s) {
6099 			var t = this;
6100 
6101 			t.win = t.doc = t.root = t.events = t.frag = null;
6102 
6103 			// Manual destroy then remove unload handler
6104 			if (!s)
6105 				tinymce.removeUnload(t.destroy);
6106 		},
6107 
6108 		createRng : function() {
6109 			var d = this.doc;
6110 
6111 			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6112 		},
6113 
6114 		nodeIndex : function(node, normalized) {
6115 			var idx = 0, lastNodeType, lastNode, nodeType;
6116 
6117 			if (node) {
6118 				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6119 					nodeType = node.nodeType;
6120 
6121 					// Normalize text nodes
6122 					if (normalized && nodeType == 3) {
6123 						if (nodeType == lastNodeType || !node.nodeValue.length)
6124 							continue;
6125 					}
6126 					idx++;
6127 					lastNodeType = nodeType;
6128 				}
6129 			}
6130 
6131 			return idx;
6132 		},
6133 
6134 		split : function(pe, e, re) {
6135 			var t = this, r = t.createRng(), bef, aft, pa;
6136 
6137 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6138 			// but we don't want that in our code since it serves no purpose for the end user
6139 			// For example if this is chopped:
6140 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6141 			// would produce:
6142 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6143 			// this function will then trim of empty edges and produce:
6144 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6145 			function trim(node) {
6146 				var i, children = node.childNodes, type = node.nodeType;
6147 
6148 				function surroundedBySpans(node) {
6149 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6150 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6151 					return previousIsSpan && nextIsSpan;
6152 				}
6153 
6154 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6155 					return;
6156 
6157 				for (i = children.length - 1; i >= 0; i--)
6158 					trim(children[i]);
6159 
6160 				if (type != 9) {
6161 					// Keep non whitespace text nodes
6162 					if (type == 3 && node.nodeValue.length > 0) {
6163 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6164 						// Also keep text nodes with only spaces if surrounded by spans.
6165 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6166 						var trimmedLength = tinymce.trim(node.nodeValue).length;
6167 						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6168 							return;
6169 					} else if (type == 1) {
6170 						// If the only child is a bookmark then move it up
6171 						children = node.childNodes;
6172 						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6173 							node.parentNode.insertBefore(children[0], node);
6174 
6175 						// Keep non empty elements or img, hr etc
6176 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6177 							return;
6178 					}
6179 
6180 					t.remove(node);
6181 				}
6182 
6183 				return node;
6184 			};
6185 
6186 			if (pe && e) {
6187 				// Get before chunk
6188 				r.setStart(pe.parentNode, t.nodeIndex(pe));
6189 				r.setEnd(e.parentNode, t.nodeIndex(e));
6190 				bef = r.extractContents();
6191 
6192 				// Get after chunk
6193 				r = t.createRng();
6194 				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6195 				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6196 				aft = r.extractContents();
6197 
6198 				// Insert before chunk
6199 				pa = pe.parentNode;
6200 				pa.insertBefore(trim(bef), pe);
6201 
6202 				// Insert middle chunk
6203 				if (re)
6204 				pa.replaceChild(re, e);
6205 			else
6206 				pa.insertBefore(e, pe);
6207 
6208 				// Insert after chunk
6209 				pa.insertBefore(trim(aft), pe);
6210 				t.remove(pe);
6211 
6212 				return re || e;
6213 			}
6214 		},
6215 
6216 		bind : function(target, name, func, scope) {
6217 			return this.events.add(target, name, func, scope || this);
6218 		},
6219 
6220 		unbind : function(target, name, func) {
6221 			return this.events.remove(target, name, func);
6222 		},
6223 
6224 		fire : function(target, name, evt) {
6225 			return this.events.fire(target, name, evt);
6226 		},
6227 
6228 		// Returns the content editable state of a node
6229 		getContentEditable: function(node) {
6230 			var contentEditable;
6231 
6232 			// Check type
6233 			if (node.nodeType != 1) {
6234 				return null;
6235 			}
6236 
6237 			// Check for fake content editable
6238 			contentEditable = node.getAttribute("data-mce-contenteditable");
6239 			if (contentEditable && contentEditable !== "inherit") {
6240 				return contentEditable;
6241 			}
6242 
6243 			// Check for real content editable
6244 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6245 		},
6246 
6247 
6248 		_findSib : function(node, selector, name) {
6249 			var t = this, f = selector;
6250 
6251 			if (node) {
6252 				// If expression make a function of it using is
6253 				if (is(f, 'string')) {
6254 					f = function(node) {
6255 						return t.is(node, selector);
6256 					};
6257 				}
6258 
6259 				// Loop all siblings
6260 				for (node = node[name]; node; node = node[name]) {
6261 					if (f(node))
6262 						return node;
6263 				}
6264 			}
6265 
6266 			return null;
6267 		},
6268 
6269 		_isRes : function(c) {
6270 			// Is live resizble element
6271 			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6272 		}
6273 
6274 		/*
6275 		walk : function(n, f, s) {
6276 			var d = this.doc, w;
6277 
6278 			if (d.createTreeWalker) {
6279 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6280 
6281 				while ((n = w.nextNode()) != null)
6282 					f.call(s || this, n);
6283 			} else
6284 				tinymce.walk(n, f, 'childNodes', s);
6285 		}
6286 		*/
6287 
6288 		/*
6289 		toRGB : function(s) {
6290 			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6291 
6292 			if (c) {
6293 				// #FFF -> #FFFFFF
6294 				if (!is(c[3]))
6295 					c[3] = c[2] = c[1];
6296 
6297 				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6298 			}
6299 
6300 			return s;
6301 		}
6302 		*/
6303 	});
6304 
6305 	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6306 })(tinymce);
6307 
6308 (function(ns) {
6309 	// Range constructor
6310 	function Range(dom) {
6311 		var t = this,
6312 			doc = dom.doc,
6313 			EXTRACT = 0,
6314 			CLONE = 1,
6315 			DELETE = 2,
6316 			TRUE = true,
6317 			FALSE = false,
6318 			START_OFFSET = 'startOffset',
6319 			START_CONTAINER = 'startContainer',
6320 			END_CONTAINER = 'endContainer',
6321 			END_OFFSET = 'endOffset',
6322 			extend = tinymce.extend,
6323 			nodeIndex = dom.nodeIndex;
6324 
6325 		extend(t, {
6326 			// Inital states
6327 			startContainer : doc,
6328 			startOffset : 0,
6329 			endContainer : doc,
6330 			endOffset : 0,
6331 			collapsed : TRUE,
6332 			commonAncestorContainer : doc,
6333 
6334 			// Range constants
6335 			START_TO_START : 0,
6336 			START_TO_END : 1,
6337 			END_TO_END : 2,
6338 			END_TO_START : 3,
6339 
6340 			// Public methods
6341 			setStart : setStart,
6342 			setEnd : setEnd,
6343 			setStartBefore : setStartBefore,
6344 			setStartAfter : setStartAfter,
6345 			setEndBefore : setEndBefore,
6346 			setEndAfter : setEndAfter,
6347 			collapse : collapse,
6348 			selectNode : selectNode,
6349 			selectNodeContents : selectNodeContents,
6350 			compareBoundaryPoints : compareBoundaryPoints,
6351 			deleteContents : deleteContents,
6352 			extractContents : extractContents,
6353 			cloneContents : cloneContents,
6354 			insertNode : insertNode,
6355 			surroundContents : surroundContents,
6356 			cloneRange : cloneRange,
6357 			toStringIE : toStringIE
6358 		});
6359 
6360 		function createDocumentFragment() {
6361 			return doc.createDocumentFragment();
6362 		};
6363 
6364 		function setStart(n, o) {
6365 			_setEndPoint(TRUE, n, o);
6366 		};
6367 
6368 		function setEnd(n, o) {
6369 			_setEndPoint(FALSE, n, o);
6370 		};
6371 
6372 		function setStartBefore(n) {
6373 			setStart(n.parentNode, nodeIndex(n));
6374 		};
6375 
6376 		function setStartAfter(n) {
6377 			setStart(n.parentNode, nodeIndex(n) + 1);
6378 		};
6379 
6380 		function setEndBefore(n) {
6381 			setEnd(n.parentNode, nodeIndex(n));
6382 		};
6383 
6384 		function setEndAfter(n) {
6385 			setEnd(n.parentNode, nodeIndex(n) + 1);
6386 		};
6387 
6388 		function collapse(ts) {
6389 			if (ts) {
6390 				t[END_CONTAINER] = t[START_CONTAINER];
6391 				t[END_OFFSET] = t[START_OFFSET];
6392 			} else {
6393 				t[START_CONTAINER] = t[END_CONTAINER];
6394 				t[START_OFFSET] = t[END_OFFSET];
6395 			}
6396 
6397 			t.collapsed = TRUE;
6398 		};
6399 
6400 		function selectNode(n) {
6401 			setStartBefore(n);
6402 			setEndAfter(n);
6403 		};
6404 
6405 		function selectNodeContents(n) {
6406 			setStart(n, 0);
6407 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6408 		};
6409 
6410 		function compareBoundaryPoints(h, r) {
6411 			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6412 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6413 
6414 			// Check START_TO_START
6415 			if (h === 0)
6416 				return _compareBoundaryPoints(sc, so, rsc, rso);
6417 	
6418 			// Check START_TO_END
6419 			if (h === 1)
6420 				return _compareBoundaryPoints(ec, eo, rsc, rso);
6421 	
6422 			// Check END_TO_END
6423 			if (h === 2)
6424 				return _compareBoundaryPoints(ec, eo, rec, reo);
6425 	
6426 			// Check END_TO_START
6427 			if (h === 3) 
6428 				return _compareBoundaryPoints(sc, so, rec, reo);
6429 		};
6430 
6431 		function deleteContents() {
6432 			_traverse(DELETE);
6433 		};
6434 
6435 		function extractContents() {
6436 			return _traverse(EXTRACT);
6437 		};
6438 
6439 		function cloneContents() {
6440 			return _traverse(CLONE);
6441 		};
6442 
6443 		function insertNode(n) {
6444 			var startContainer = this[START_CONTAINER],
6445 				startOffset = this[START_OFFSET], nn, o;
6446 
6447 			// Node is TEXT_NODE or CDATA
6448 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6449 				if (!startOffset) {
6450 					// At the start of text
6451 					startContainer.parentNode.insertBefore(n, startContainer);
6452 				} else if (startOffset >= startContainer.nodeValue.length) {
6453 					// At the end of text
6454 					dom.insertAfter(n, startContainer);
6455 				} else {
6456 					// Middle, need to split
6457 					nn = startContainer.splitText(startOffset);
6458 					startContainer.parentNode.insertBefore(n, nn);
6459 				}
6460 			} else {
6461 				// Insert element node
6462 				if (startContainer.childNodes.length > 0)
6463 					o = startContainer.childNodes[startOffset];
6464 
6465 				if (o)
6466 					startContainer.insertBefore(n, o);
6467 				else
6468 					startContainer.appendChild(n);
6469 			}
6470 		};
6471 
6472 		function surroundContents(n) {
6473 			var f = t.extractContents();
6474 
6475 			t.insertNode(n);
6476 			n.appendChild(f);
6477 			t.selectNode(n);
6478 		};
6479 
6480 		function cloneRange() {
6481 			return extend(new Range(dom), {
6482 				startContainer : t[START_CONTAINER],
6483 				startOffset : t[START_OFFSET],
6484 				endContainer : t[END_CONTAINER],
6485 				endOffset : t[END_OFFSET],
6486 				collapsed : t.collapsed,
6487 				commonAncestorContainer : t.commonAncestorContainer
6488 			});
6489 		};
6490 
6491 		// Private methods
6492 
6493 		function _getSelectedNode(container, offset) {
6494 			var child;
6495 
6496 			if (container.nodeType == 3 /* TEXT_NODE */)
6497 				return container;
6498 
6499 			if (offset < 0)
6500 				return container;
6501 
6502 			child = container.firstChild;
6503 			while (child && offset > 0) {
6504 				--offset;
6505 				child = child.nextSibling;
6506 			}
6507 
6508 			if (child)
6509 				return child;
6510 
6511 			return container;
6512 		};
6513 
6514 		function _isCollapsed() {
6515 			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6516 		};
6517 
6518 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6519 			var c, offsetC, n, cmnRoot, childA, childB;
6520 			
6521 			// In the first case the boundary-points have the same container. A is before B
6522 			// if its offset is less than the offset of B, A is equal to B if its offset is
6523 			// equal to the offset of B, and A is after B if its offset is greater than the
6524 			// offset of B.
6525 			if (containerA == containerB) {
6526 				if (offsetA == offsetB)
6527 					return 0; // equal
6528 
6529 				if (offsetA < offsetB)
6530 					return -1; // before
6531 
6532 				return 1; // after
6533 			}
6534 
6535 			// In the second case a child node C of the container of A is an ancestor
6536 			// container of B. In this case, A is before B if the offset of A is less than or
6537 			// equal to the index of the child node C and A is after B otherwise.
6538 			c = containerB;
6539 			while (c && c.parentNode != containerA)
6540 				c = c.parentNode;
6541 
6542 			if (c) {
6543 				offsetC = 0;
6544 				n = containerA.firstChild;
6545 
6546 				while (n != c && offsetC < offsetA) {
6547 					offsetC++;
6548 					n = n.nextSibling;
6549 				}
6550 
6551 				if (offsetA <= offsetC)
6552 					return -1; // before
6553 
6554 				return 1; // after
6555 			}
6556 
6557 			// In the third case a child node C of the container of B is an ancestor container
6558 			// of A. In this case, A is before B if the index of the child node C is less than
6559 			// the offset of B and A is after B otherwise.
6560 			c = containerA;
6561 			while (c && c.parentNode != containerB) {
6562 				c = c.parentNode;
6563 			}
6564 
6565 			if (c) {
6566 				offsetC = 0;
6567 				n = containerB.firstChild;
6568 
6569 				while (n != c && offsetC < offsetB) {
6570 					offsetC++;
6571 					n = n.nextSibling;
6572 				}
6573 
6574 				if (offsetC < offsetB)
6575 					return -1; // before
6576 
6577 				return 1; // after
6578 			}
6579 
6580 			// In the fourth case, none of three other cases hold: the containers of A and B
6581 			// are siblings or descendants of sibling nodes. In this case, A is before B if
6582 			// the container of A is before the container of B in a pre-order traversal of the
6583 			// Ranges' context tree and A is after B otherwise.
6584 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6585 			childA = containerA;
6586 
6587 			while (childA && childA.parentNode != cmnRoot)
6588 				childA = childA.parentNode;
6589 
6590 			if (!childA)
6591 				childA = cmnRoot;
6592 
6593 			childB = containerB;
6594 			while (childB && childB.parentNode != cmnRoot)
6595 				childB = childB.parentNode;
6596 
6597 			if (!childB)
6598 				childB = cmnRoot;
6599 
6600 			if (childA == childB)
6601 				return 0; // equal
6602 
6603 			n = cmnRoot.firstChild;
6604 			while (n) {
6605 				if (n == childA)
6606 					return -1; // before
6607 
6608 				if (n == childB)
6609 					return 1; // after
6610 
6611 				n = n.nextSibling;
6612 			}
6613 		};
6614 
6615 		function _setEndPoint(st, n, o) {
6616 			var ec, sc;
6617 
6618 			if (st) {
6619 				t[START_CONTAINER] = n;
6620 				t[START_OFFSET] = o;
6621 			} else {
6622 				t[END_CONTAINER] = n;
6623 				t[END_OFFSET] = o;
6624 			}
6625 
6626 			// If one boundary-point of a Range is set to have a root container
6627 			// other than the current one for the Range, the Range is collapsed to
6628 			// the new position. This enforces the restriction that both boundary-
6629 			// points of a Range must have the same root container.
6630 			ec = t[END_CONTAINER];
6631 			while (ec.parentNode)
6632 				ec = ec.parentNode;
6633 
6634 			sc = t[START_CONTAINER];
6635 			while (sc.parentNode)
6636 				sc = sc.parentNode;
6637 
6638 			if (sc == ec) {
6639 				// The start position of a Range is guaranteed to never be after the
6640 				// end position. To enforce this restriction, if the start is set to
6641 				// be at a position after the end, the Range is collapsed to that
6642 				// position.
6643 				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6644 					t.collapse(st);
6645 			} else
6646 				t.collapse(st);
6647 
6648 			t.collapsed = _isCollapsed();
6649 			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6650 		};
6651 
6652 		function _traverse(how) {
6653 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6654 
6655 			if (t[START_CONTAINER] == t[END_CONTAINER])
6656 				return _traverseSameContainer(how);
6657 
6658 			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6659 				if (p == t[START_CONTAINER])
6660 					return _traverseCommonStartContainer(c, how);
6661 
6662 				++endContainerDepth;
6663 			}
6664 
6665 			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6666 				if (p == t[END_CONTAINER])
6667 					return _traverseCommonEndContainer(c, how);
6668 
6669 				++startContainerDepth;
6670 			}
6671 
6672 			depthDiff = startContainerDepth - endContainerDepth;
6673 
6674 			startNode = t[START_CONTAINER];
6675 			while (depthDiff > 0) {
6676 				startNode = startNode.parentNode;
6677 				depthDiff--;
6678 			}
6679 
6680 			endNode = t[END_CONTAINER];
6681 			while (depthDiff < 0) {
6682 				endNode = endNode.parentNode;
6683 				depthDiff++;
6684 			}
6685 
6686 			// ascend the ancestor hierarchy until we have a common parent.
6687 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6688 				startNode = sp;
6689 				endNode = ep;
6690 			}
6691 
6692 			return _traverseCommonAncestors(startNode, endNode, how);
6693 		};
6694 
6695 		 function _traverseSameContainer(how) {
6696 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6697 
6698 			if (how != DELETE)
6699 				frag = createDocumentFragment();
6700 
6701 			// If selection is empty, just return the fragment
6702 			if (t[START_OFFSET] == t[END_OFFSET])
6703 				return frag;
6704 
6705 			// Text node needs special case handling
6706 			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6707 				// get the substring
6708 				s = t[START_CONTAINER].nodeValue;
6709 				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6710 
6711 				// set the original text node to its new value
6712 				if (how != CLONE) {
6713 					n = t[START_CONTAINER];
6714 					start = t[START_OFFSET];
6715 					len = t[END_OFFSET] - t[START_OFFSET];
6716 
6717 					if (start === 0 && len >= n.nodeValue.length - 1) {
6718 						n.parentNode.removeChild(n);
6719 					} else {
6720 						n.deleteData(start, len);
6721 					}
6722 
6723 					// Nothing is partially selected, so collapse to start point
6724 					t.collapse(TRUE);
6725 				}
6726 
6727 				if (how == DELETE)
6728 					return;
6729 
6730 				if (sub.length > 0) {
6731 					frag.appendChild(doc.createTextNode(sub));
6732 				}
6733 
6734 				return frag;
6735 			}
6736 
6737 			// Copy nodes between the start/end offsets.
6738 			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6739 			cnt = t[END_OFFSET] - t[START_OFFSET];
6740 
6741 			while (n && cnt > 0) {
6742 				sibling = n.nextSibling;
6743 				xferNode = _traverseFullySelected(n, how);
6744 
6745 				if (frag)
6746 					frag.appendChild( xferNode );
6747 
6748 				--cnt;
6749 				n = sibling;
6750 			}
6751 
6752 			// Nothing is partially selected, so collapse to start point
6753 			if (how != CLONE)
6754 				t.collapse(TRUE);
6755 
6756 			return frag;
6757 		};
6758 
6759 		function _traverseCommonStartContainer(endAncestor, how) {
6760 			var frag, n, endIdx, cnt, sibling, xferNode;
6761 
6762 			if (how != DELETE)
6763 				frag = createDocumentFragment();
6764 
6765 			n = _traverseRightBoundary(endAncestor, how);
6766 
6767 			if (frag)
6768 				frag.appendChild(n);
6769 
6770 			endIdx = nodeIndex(endAncestor);
6771 			cnt = endIdx - t[START_OFFSET];
6772 
6773 			if (cnt <= 0) {
6774 				// Collapse to just before the endAncestor, which
6775 				// is partially selected.
6776 				if (how != CLONE) {
6777 					t.setEndBefore(endAncestor);
6778 					t.collapse(FALSE);
6779 				}
6780 
6781 				return frag;
6782 			}
6783 
6784 			n = endAncestor.previousSibling;
6785 			while (cnt > 0) {
6786 				sibling = n.previousSibling;
6787 				xferNode = _traverseFullySelected(n, how);
6788 
6789 				if (frag)
6790 					frag.insertBefore(xferNode, frag.firstChild);
6791 
6792 				--cnt;
6793 				n = sibling;
6794 			}
6795 
6796 			// Collapse to just before the endAncestor, which
6797 			// is partially selected.
6798 			if (how != CLONE) {
6799 				t.setEndBefore(endAncestor);
6800 				t.collapse(FALSE);
6801 			}
6802 
6803 			return frag;
6804 		};
6805 
6806 		function _traverseCommonEndContainer(startAncestor, how) {
6807 			var frag, startIdx, n, cnt, sibling, xferNode;
6808 
6809 			if (how != DELETE)
6810 				frag = createDocumentFragment();
6811 
6812 			n = _traverseLeftBoundary(startAncestor, how);
6813 			if (frag)
6814 				frag.appendChild(n);
6815 
6816 			startIdx = nodeIndex(startAncestor);
6817 			++startIdx; // Because we already traversed it
6818 
6819 			cnt = t[END_OFFSET] - startIdx;
6820 			n = startAncestor.nextSibling;
6821 			while (n && cnt > 0) {
6822 				sibling = n.nextSibling;
6823 				xferNode = _traverseFullySelected(n, how);
6824 
6825 				if (frag)
6826 					frag.appendChild(xferNode);
6827 
6828 				--cnt;
6829 				n = sibling;
6830 			}
6831 
6832 			if (how != CLONE) {
6833 				t.setStartAfter(startAncestor);
6834 				t.collapse(TRUE);
6835 			}
6836 
6837 			return frag;
6838 		};
6839 
6840 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
6841 			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
6842 
6843 			if (how != DELETE)
6844 				frag = createDocumentFragment();
6845 
6846 			n = _traverseLeftBoundary(startAncestor, how);
6847 			if (frag)
6848 				frag.appendChild(n);
6849 
6850 			commonParent = startAncestor.parentNode;
6851 			startOffset = nodeIndex(startAncestor);
6852 			endOffset = nodeIndex(endAncestor);
6853 			++startOffset;
6854 
6855 			cnt = endOffset - startOffset;
6856 			sibling = startAncestor.nextSibling;
6857 
6858 			while (cnt > 0) {
6859 				nextSibling = sibling.nextSibling;
6860 				n = _traverseFullySelected(sibling, how);
6861 
6862 				if (frag)
6863 					frag.appendChild(n);
6864 
6865 				sibling = nextSibling;
6866 				--cnt;
6867 			}
6868 
6869 			n = _traverseRightBoundary(endAncestor, how);
6870 
6871 			if (frag)
6872 				frag.appendChild(n);
6873 
6874 			if (how != CLONE) {
6875 				t.setStartAfter(startAncestor);
6876 				t.collapse(TRUE);
6877 			}
6878 
6879 			return frag;
6880 		};
6881 
6882 		function _traverseRightBoundary(root, how) {
6883 			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
6884 
6885 			if (next == root)
6886 				return _traverseNode(next, isFullySelected, FALSE, how);
6887 
6888 			parent = next.parentNode;
6889 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
6890 
6891 			while (parent) {
6892 				while (next) {
6893 					prevSibling = next.previousSibling;
6894 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
6895 
6896 					if (how != DELETE)
6897 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
6898 
6899 					isFullySelected = TRUE;
6900 					next = prevSibling;
6901 				}
6902 
6903 				if (parent == root)
6904 					return clonedParent;
6905 
6906 				next = parent.previousSibling;
6907 				parent = parent.parentNode;
6908 
6909 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
6910 
6911 				if (how != DELETE)
6912 					clonedGrandParent.appendChild(clonedParent);
6913 
6914 				clonedParent = clonedGrandParent;
6915 			}
6916 		};
6917 
6918 		function _traverseLeftBoundary(root, how) {
6919 			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
6920 
6921 			if (next == root)
6922 				return _traverseNode(next, isFullySelected, TRUE, how);
6923 
6924 			parent = next.parentNode;
6925 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
6926 
6927 			while (parent) {
6928 				while (next) {
6929 					nextSibling = next.nextSibling;
6930 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
6931 
6932 					if (how != DELETE)
6933 						clonedParent.appendChild(clonedChild);
6934 
6935 					isFullySelected = TRUE;
6936 					next = nextSibling;
6937 				}
6938 
6939 				if (parent == root)
6940 					return clonedParent;
6941 
6942 				next = parent.nextSibling;
6943 				parent = parent.parentNode;
6944 
6945 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
6946 
6947 				if (how != DELETE)
6948 					clonedGrandParent.appendChild(clonedParent);
6949 
6950 				clonedParent = clonedGrandParent;
6951 			}
6952 		};
6953 
6954 		function _traverseNode(n, isFullySelected, isLeft, how) {
6955 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
6956 
6957 			if (isFullySelected)
6958 				return _traverseFullySelected(n, how);
6959 
6960 			if (n.nodeType == 3 /* TEXT_NODE */) {
6961 				txtValue = n.nodeValue;
6962 
6963 				if (isLeft) {
6964 					offset = t[START_OFFSET];
6965 					newNodeValue = txtValue.substring(offset);
6966 					oldNodeValue = txtValue.substring(0, offset);
6967 				} else {
6968 					offset = t[END_OFFSET];
6969 					newNodeValue = txtValue.substring(0, offset);
6970 					oldNodeValue = txtValue.substring(offset);
6971 				}
6972 
6973 				if (how != CLONE)
6974 					n.nodeValue = oldNodeValue;
6975 
6976 				if (how == DELETE)
6977 					return;
6978 
6979 				newNode = dom.clone(n, FALSE);
6980 				newNode.nodeValue = newNodeValue;
6981 
6982 				return newNode;
6983 			}
6984 
6985 			if (how == DELETE)
6986 				return;
6987 
6988 			return dom.clone(n, FALSE);
6989 		};
6990 
6991 		function _traverseFullySelected(n, how) {
6992 			if (how != DELETE)
6993 				return how == CLONE ? dom.clone(n, TRUE) : n;
6994 
6995 			n.parentNode.removeChild(n);
6996 		};
6997 
6998 		function toStringIE() {
6999 			return dom.create('body', null, cloneContents()).outerText;
7000 		}
7001 		
7002 		return t;
7003 	};
7004 
7005 	ns.Range = Range;
7006 
7007 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7008 	Range.prototype.toString = function() {
7009 		return this.toStringIE();
7010 	};
7011 })(tinymce.dom);
7012 
7013 (function() {
7014 	function Selection(selection) {
7015 		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7016 
7017 		function getPosition(rng, start) {
7018 			var checkRng, startIndex = 0, endIndex, inside,
7019 				children, child, offset, index, position = -1, parent;
7020 
7021 			// Setup test range, collapse it and get the parent
7022 			checkRng = rng.duplicate();
7023 			checkRng.collapse(start);
7024 			parent = checkRng.parentElement();
7025 
7026 			// Check if the selection is within the right document
7027 			if (parent.ownerDocument !== selection.dom.doc)
7028 				return;
7029 
7030 			// IE will report non editable elements as it's parent so look for an editable one
7031 			while (parent.contentEditable === "false") {
7032 				parent = parent.parentNode;
7033 			}
7034 
7035 			// If parent doesn't have any children then return that we are inside the element
7036 			if (!parent.hasChildNodes()) {
7037 				return {node : parent, inside : 1};
7038 			}
7039 
7040 			// Setup node list and endIndex
7041 			children = parent.children;
7042 			endIndex = children.length - 1;
7043 
7044 			// Perform a binary search for the position
7045 			while (startIndex <= endIndex) {
7046 				index = Math.floor((startIndex + endIndex) / 2);
7047 
7048 				// Move selection to node and compare the ranges
7049 				child = children[index];
7050 				checkRng.moveToElementText(child);
7051 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7052 
7053 				// Before/after or an exact match
7054 				if (position > 0) {
7055 					endIndex = index - 1;
7056 				} else if (position < 0) {
7057 					startIndex = index + 1;
7058 				} else {
7059 					return {node : child};
7060 				}
7061 			}
7062 
7063 			// Check if child position is before or we didn't find a position
7064 			if (position < 0) {
7065 				// No element child was found use the parent element and the offset inside that
7066 				if (!child) {
7067 					checkRng.moveToElementText(parent);
7068 					checkRng.collapse(true);
7069 					child = parent;
7070 					inside = true;
7071 				} else
7072 					checkRng.collapse(false);
7073 
7074 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7075 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7076 				offset = 0;
7077 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7078 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7079 						break;
7080 					}
7081 
7082 					offset++;
7083 				}
7084 			} else {
7085 				// Child position is after the selection endpoint
7086 				checkRng.collapse(true);
7087 
7088 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7089 				offset = 0;
7090 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7091 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7092 						break;
7093 					}
7094 
7095 					offset++;
7096 				}
7097 			}
7098 
7099 			return {node : child, position : position, offset : offset, inside : inside};
7100 		};
7101 
7102 		// Returns a W3C DOM compatible range object by using the IE Range API
7103 		function getRange() {
7104 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7105 
7106 			// If selection is outside the current document just return an empty range
7107 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7108 			if (element.ownerDocument != dom.doc)
7109 				return domRange;
7110 
7111 			collapsed = selection.isCollapsed();
7112 
7113 			// Handle control selection
7114 			if (ieRange.item) {
7115 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7116 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7117 
7118 				return domRange;
7119 			}
7120 
7121 			function findEndPoint(start) {
7122 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7123 
7124 				container = endPoint.node;
7125 				offset = endPoint.offset;
7126 
7127 				if (endPoint.inside && !container.hasChildNodes()) {
7128 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7129 					return;
7130 				}
7131 
7132 				if (offset === undef) {
7133 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7134 					return;
7135 				}
7136 
7137 				if (endPoint.position < 0) {
7138 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7139 
7140 					if (!sibling) {
7141 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7142 						return;
7143 					}
7144 
7145 					if (!offset) {
7146 						if (sibling.nodeType == 3)
7147 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7148 						else
7149 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7150 
7151 						return;
7152 					}
7153 
7154 					// Find the text node and offset
7155 					while (sibling) {
7156 						nodeValue = sibling.nodeValue;
7157 						textNodeOffset += nodeValue.length;
7158 
7159 						// We are at or passed the position we where looking for
7160 						if (textNodeOffset >= offset) {
7161 							container = sibling;
7162 							textNodeOffset -= offset;
7163 							textNodeOffset = nodeValue.length - textNodeOffset;
7164 							break;
7165 						}
7166 
7167 						sibling = sibling.nextSibling;
7168 					}
7169 				} else {
7170 					// Find the text node and offset
7171 					sibling = container.previousSibling;
7172 
7173 					if (!sibling)
7174 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7175 
7176 					// If there isn't any text to loop then use the first position
7177 					if (!offset) {
7178 						if (container.nodeType == 3)
7179 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7180 						else
7181 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7182 
7183 						return;
7184 					}
7185 
7186 					while (sibling) {
7187 						textNodeOffset += sibling.nodeValue.length;
7188 
7189 						// We are at or passed the position we where looking for
7190 						if (textNodeOffset >= offset) {
7191 							container = sibling;
7192 							textNodeOffset -= offset;
7193 							break;
7194 						}
7195 
7196 						sibling = sibling.previousSibling;
7197 					}
7198 				}
7199 
7200 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7201 			};
7202 
7203 			try {
7204 				// Find start point
7205 				findEndPoint(true);
7206 
7207 				// Find end point if needed
7208 				if (!collapsed)
7209 					findEndPoint();
7210 			} catch (ex) {
7211 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7212 				// access the nodeValue or other properties of text nodes. This seems to happend when
7213 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7214 				if (ex.number == -2147024809) {
7215 					// Get the current selection
7216 					bookmark = self.getBookmark(2);
7217 
7218 					// Get start element
7219 					tmpRange = ieRange.duplicate();
7220 					tmpRange.collapse(true);
7221 					element = tmpRange.parentElement();
7222 
7223 					// Get end element
7224 					if (!collapsed) {
7225 						tmpRange = ieRange.duplicate();
7226 						tmpRange.collapse(false);
7227 						element2 = tmpRange.parentElement();
7228 						element2.innerHTML = element2.innerHTML;
7229 					}
7230 
7231 					// Remove the broken elements
7232 					element.innerHTML = element.innerHTML;
7233 
7234 					// Restore the selection
7235 					self.moveToBookmark(bookmark);
7236 
7237 					// Since the range has moved we need to re-get it
7238 					ieRange = selection.getRng();
7239 
7240 					// Find start point
7241 					findEndPoint(true);
7242 
7243 					// Find end point if needed
7244 					if (!collapsed)
7245 						findEndPoint();
7246 				} else
7247 					throw ex; // Throw other errors
7248 			}
7249 
7250 			return domRange;
7251 		};
7252 
7253 		this.getBookmark = function(type) {
7254 			var rng = selection.getRng(), start, end, bookmark = {};
7255 
7256 			function getIndexes(node) {
7257 				var parent, root, children, i, indexes = [];
7258 
7259 				parent = node.parentNode;
7260 				root = dom.getRoot().parentNode;
7261 
7262 				while (parent != root && parent.nodeType !== 9) {
7263 					children = parent.children;
7264 
7265 					i = children.length;
7266 					while (i--) {
7267 						if (node === children[i]) {
7268 							indexes.push(i);
7269 							break;
7270 						}
7271 					}
7272 
7273 					node = parent;
7274 					parent = parent.parentNode;
7275 				}
7276 
7277 				return indexes;
7278 			};
7279 
7280 			function getBookmarkEndPoint(start) {
7281 				var position;
7282 
7283 				position = getPosition(rng, start);
7284 				if (position) {
7285 					return {
7286 						position : position.position,
7287 						offset : position.offset,
7288 						indexes : getIndexes(position.node),
7289 						inside : position.inside
7290 					};
7291 				}
7292 			};
7293 
7294 			// Non ubstructive bookmark
7295 			if (type === 2) {
7296 				// Handle text selection
7297 				if (!rng.item) {
7298 					bookmark.start = getBookmarkEndPoint(true);
7299 
7300 					if (!selection.isCollapsed())
7301 						bookmark.end = getBookmarkEndPoint();
7302 				} else
7303 					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7304 			}
7305 
7306 			return bookmark;
7307 		};
7308 
7309 		this.moveToBookmark = function(bookmark) {
7310 			var rng, body = dom.doc.body;
7311 
7312 			function resolveIndexes(indexes) {
7313 				var node, i, idx, children;
7314 
7315 				node = dom.getRoot();
7316 				for (i = indexes.length - 1; i >= 0; i--) {
7317 					children = node.children;
7318 					idx = indexes[i];
7319 
7320 					if (idx <= children.length - 1) {
7321 						node = children[idx];
7322 					}
7323 				}
7324 
7325 				return node;
7326 			};
7327 			
7328 			function setBookmarkEndPoint(start) {
7329 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7330 
7331 				if (endPoint) {
7332 					moveLeft = endPoint.position > 0;
7333 
7334 					moveRng = body.createTextRange();
7335 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7336 
7337 					offset = endPoint.offset;
7338 					if (offset !== undef) {
7339 						moveRng.collapse(endPoint.inside || moveLeft);
7340 						moveRng.moveStart('character', moveLeft ? -offset : offset);
7341 					} else
7342 						moveRng.collapse(start);
7343 
7344 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7345 
7346 					if (start)
7347 						rng.collapse(true);
7348 				}
7349 			};
7350 
7351 			if (bookmark.start) {
7352 				if (bookmark.start.ctrl) {
7353 					rng = body.createControlRange();
7354 					rng.addElement(resolveIndexes(bookmark.start.indexes));
7355 					rng.select();
7356 				} else {
7357 					rng = body.createTextRange();
7358 					setBookmarkEndPoint(true);
7359 					setBookmarkEndPoint();
7360 					rng.select();
7361 				}
7362 			}
7363 		};
7364 
7365 		this.addRange = function(rng) {
7366 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
7367 
7368 			function setEndPoint(start) {
7369 				var container, offset, marker, tmpRng, nodes;
7370 
7371 				marker = dom.create('a');
7372 				container = start ? startContainer : endContainer;
7373 				offset = start ? startOffset : endOffset;
7374 				tmpRng = ieRng.duplicate();
7375 
7376 				if (container == doc || container == doc.documentElement) {
7377 					container = body;
7378 					offset = 0;
7379 				}
7380 
7381 				if (container.nodeType == 3) {
7382 					container.parentNode.insertBefore(marker, container);
7383 					tmpRng.moveToElementText(marker);
7384 					tmpRng.moveStart('character', offset);
7385 					dom.remove(marker);
7386 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7387 				} else {
7388 					nodes = container.childNodes;
7389 
7390 					if (nodes.length) {
7391 						if (offset >= nodes.length) {
7392 							dom.insertAfter(marker, nodes[nodes.length - 1]);
7393 						} else {
7394 							container.insertBefore(marker, nodes[offset]);
7395 						}
7396 
7397 						tmpRng.moveToElementText(marker);
7398 					} else if (container.canHaveHTML) {
7399 						// Empty node selection for example <div>|</div>
7400 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7401 						container.innerHTML = '<span>\uFEFF</span>';
7402 						marker = container.firstChild;
7403 						tmpRng.moveToElementText(marker);
7404 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7405 					}
7406 
7407 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7408 					dom.remove(marker);
7409 				}
7410 			}
7411 
7412 			// Setup some shorter versions
7413 			startContainer = rng.startContainer;
7414 			startOffset = rng.startOffset;
7415 			endContainer = rng.endContainer;
7416 			endOffset = rng.endOffset;
7417 			ieRng = body.createTextRange();
7418 
7419 			// If single element selection then try making a control selection out of it
7420 			if (startContainer == endContainer && startContainer.nodeType == 1) {
7421 				// Trick to place the caret inside an empty block element like <p></p>
7422 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7423 					if (startContainer.canHaveHTML) {
7424 						// Check if previous sibling is an empty block if it is then we need to render it
7425 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7426 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7427 						sibling = startContainer.previousSibling;
7428 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7429 							sibling.innerHTML = '\uFEFF';
7430 						} else {
7431 							sibling = null;
7432 						}
7433 
7434 						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7435 						ieRng.moveToElementText(startContainer.lastChild);
7436 						ieRng.select();
7437 						dom.doc.selection.clear();
7438 						startContainer.innerHTML = '';
7439 
7440 						if (sibling) {
7441 							sibling.innerHTML = '';
7442 						}
7443 						return;
7444 					} else {
7445 						startOffset = dom.nodeIndex(startContainer);
7446 						startContainer = startContainer.parentNode;
7447 					}
7448 				}
7449 
7450 				if (startOffset == endOffset - 1) {
7451 					try {
7452 						ctrlRng = body.createControlRange();
7453 						ctrlRng.addElement(startContainer.childNodes[startOffset]);
7454 						ctrlRng.select();
7455 						return;
7456 					} catch (ex) {
7457 						// Ignore
7458 					}
7459 				}
7460 			}
7461 
7462 			// Set start/end point of selection
7463 			setEndPoint(true);
7464 			setEndPoint();
7465 
7466 			// Select the new range and scroll it into view
7467 			ieRng.select();
7468 		};
7469 
7470 		// Expose range method
7471 		this.getRangeAt = getRange;
7472 	};
7473 
7474 	// Expose the selection object
7475 	tinymce.dom.TridentSelection = Selection;
7476 })();
7477 
7478 
7479 /*
7480  * Sizzle CSS Selector Engine
7481  *  Copyright, The Dojo Foundation
7482  *  Released under the MIT, BSD, and GPL Licenses.
7483  *  More information: http://sizzlejs.com/
7484  */
7485 (function(){
7486 
7487 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
7488 	expando = "sizcache",
7489 	done = 0,
7490 	toString = Object.prototype.toString,
7491 	hasDuplicate = false,
7492 	baseHasDuplicate = true,
7493 	rBackslash = /\\/g,
7494 	rReturn = /\r\n/g,
7495 	rNonWord = /\W/;
7496 
7497 // Here we check if the JavaScript engine is using some sort of
7498 // optimization where it does not always call our comparision
7499 // function. If that is the case, discard the hasDuplicate value.
7500 //   Thus far that includes Google Chrome.
7501 [0, 0].sort(function() {
7502 	baseHasDuplicate = false;
7503 	return 0;
7504 });
7505 
7506 var Sizzle = function( selector, context, results, seed ) {
7507 	results = results || [];
7508 	context = context || document;
7509 
7510 	var origContext = context;
7511 
7512 	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
7513 		return [];
7514 	}
7515 
7516 	if ( !selector || typeof selector !== "string" ) {
7517 		return results;
7518 	}
7519 
7520 	var m, set, checkSet, extra, ret, cur, pop, i,
7521 		prune = true,
7522 		contextXML = Sizzle.isXML( context ),
7523 		parts = [],
7524 		soFar = selector;
7525 
7526 	// Reset the position of the chunker regexp (start from head)
7527 	do {
7528 		chunker.exec( "" );
7529 		m = chunker.exec( soFar );
7530 
7531 		if ( m ) {
7532 			soFar = m[3];
7533 
7534 			parts.push( m[1] );
7535 
7536 			if ( m[2] ) {
7537 				extra = m[3];
7538 				break;
7539 			}
7540 		}
7541 	} while ( m );
7542 
7543 	if ( parts.length > 1 && origPOS.exec( selector ) ) {
7544 
7545 		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
7546 			set = posProcess( parts[0] + parts[1], context, seed );
7547 
7548 		} else {
7549 			set = Expr.relative[ parts[0] ] ?
7550 				[ context ] :
7551 				Sizzle( parts.shift(), context );
7552 
7553 			while ( parts.length ) {
7554 				selector = parts.shift();
7555 
7556 				if ( Expr.relative[ selector ] ) {
7557 					selector += parts.shift();
7558 				}
7559 
7560 				set = posProcess( selector, set, seed );
7561 			}
7562 		}
7563 
7564 	} else {
7565 		// Take a shortcut and set the context if the root selector is an ID
7566 		// (but not if it'll be faster if the inner selector is an ID)
7567 		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
7568 				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
7569 
7570 			ret = Sizzle.find( parts.shift(), context, contextXML );
7571 			context = ret.expr ?
7572 				Sizzle.filter( ret.expr, ret.set )[0] :
7573 				ret.set[0];
7574 		}
7575 
7576 		if ( context ) {
7577 			ret = seed ?
7578 				{ expr: parts.pop(), set: makeArray(seed) } :
7579 				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
7580 
7581 			set = ret.expr ?
7582 				Sizzle.filter( ret.expr, ret.set ) :
7583 				ret.set;
7584 
7585 			if ( parts.length > 0 ) {
7586 				checkSet = makeArray( set );
7587 
7588 			} else {
7589 				prune = false;
7590 			}
7591 
7592 			while ( parts.length ) {
7593 				cur = parts.pop();
7594 				pop = cur;
7595 
7596 				if ( !Expr.relative[ cur ] ) {
7597 					cur = "";
7598 				} else {
7599 					pop = parts.pop();
7600 				}
7601 
7602 				if ( pop == null ) {
7603 					pop = context;
7604 				}
7605 
7606 				Expr.relative[ cur ]( checkSet, pop, contextXML );
7607 			}
7608 
7609 		} else {
7610 			checkSet = parts = [];
7611 		}
7612 	}
7613 
7614 	if ( !checkSet ) {
7615 		checkSet = set;
7616 	}
7617 
7618 	if ( !checkSet ) {
7619 		Sizzle.error( cur || selector );
7620 	}
7621 
7622 	if ( toString.call(checkSet) === "[object Array]" ) {
7623 		if ( !prune ) {
7624 			results.push.apply( results, checkSet );
7625 
7626 		} else if ( context && context.nodeType === 1 ) {
7627 			for ( i = 0; checkSet[i] != null; i++ ) {
7628 				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
7629 					results.push( set[i] );
7630 				}
7631 			}
7632 
7633 		} else {
7634 			for ( i = 0; checkSet[i] != null; i++ ) {
7635 				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
7636 					results.push( set[i] );
7637 				}
7638 			}
7639 		}
7640 
7641 	} else {
7642 		makeArray( checkSet, results );
7643 	}
7644 
7645 	if ( extra ) {
7646 		Sizzle( extra, origContext, results, seed );
7647 		Sizzle.uniqueSort( results );
7648 	}
7649 
7650 	return results;
7651 };
7652 
7653 Sizzle.uniqueSort = function( results ) {
7654 	if ( sortOrder ) {
7655 		hasDuplicate = baseHasDuplicate;
7656 		results.sort( sortOrder );
7657 
7658 		if ( hasDuplicate ) {
7659 			for ( var i = 1; i < results.length; i++ ) {
7660 				if ( results[i] === results[ i - 1 ] ) {
7661 					results.splice( i--, 1 );
7662 				}
7663 			}
7664 		}
7665 	}
7666 
7667 	return results;
7668 };
7669 
7670 Sizzle.matches = function( expr, set ) {
7671 	return Sizzle( expr, null, null, set );
7672 };
7673 
7674 Sizzle.matchesSelector = function( node, expr ) {
7675 	return Sizzle( expr, null, null, [node] ).length > 0;
7676 };
7677 
7678 Sizzle.find = function( expr, context, isXML ) {
7679 	var set, i, len, match, type, left;
7680 
7681 	if ( !expr ) {
7682 		return [];
7683 	}
7684 
7685 	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
7686 		type = Expr.order[i];
7687 
7688 		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
7689 			left = match[1];
7690 			match.splice( 1, 1 );
7691 
7692 			if ( left.substr( left.length - 1 ) !== "\\" ) {
7693 				match[1] = (match[1] || "").replace( rBackslash, "" );
7694 				set = Expr.find[ type ]( match, context, isXML );
7695 
7696 				if ( set != null ) {
7697 					expr = expr.replace( Expr.match[ type ], "" );
7698 					break;
7699 				}
7700 			}
7701 		}
7702 	}
7703 
7704 	if ( !set ) {
7705 		set = typeof context.getElementsByTagName !== "undefined" ?
7706 			context.getElementsByTagName( "*" ) :
7707 			[];
7708 	}
7709 
7710 	return { set: set, expr: expr };
7711 };
7712 
7713 Sizzle.filter = function( expr, set, inplace, not ) {
7714 	var match, anyFound,
7715 		type, found, item, filter, left,
7716 		i, pass,
7717 		old = expr,
7718 		result = [],
7719 		curLoop = set,
7720 		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
7721 
7722 	while ( expr && set.length ) {
7723 		for ( type in Expr.filter ) {
7724 			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
7725 				filter = Expr.filter[ type ];
7726 				left = match[1];
7727 
7728 				anyFound = false;
7729 
7730 				match.splice(1,1);
7731 
7732 				if ( left.substr( left.length - 1 ) === "\\" ) {
7733 					continue;
7734 				}
7735 
7736 				if ( curLoop === result ) {
7737 					result = [];
7738 				}
7739 
7740 				if ( Expr.preFilter[ type ] ) {
7741 					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
7742 
7743 					if ( !match ) {
7744 						anyFound = found = true;
7745 
7746 					} else if ( match === true ) {
7747 						continue;
7748 					}
7749 				}
7750 
7751 				if ( match ) {
7752 					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
7753 						if ( item ) {
7754 							found = filter( item, match, i, curLoop );
7755 							pass = not ^ found;
7756 
7757 							if ( inplace && found != null ) {
7758 								if ( pass ) {
7759 									anyFound = true;
7760 
7761 								} else {
7762 									curLoop[i] = false;
7763 								}
7764 
7765 							} else if ( pass ) {
7766 								result.push( item );
7767 								anyFound = true;
7768 							}
7769 						}
7770 					}
7771 				}
7772 
7773 				if ( found !== undefined ) {
7774 					if ( !inplace ) {
7775 						curLoop = result;
7776 					}
7777 
7778 					expr = expr.replace( Expr.match[ type ], "" );
7779 
7780 					if ( !anyFound ) {
7781 						return [];
7782 					}
7783 
7784 					break;
7785 				}
7786 			}
7787 		}
7788 
7789 		// Improper expression
7790 		if ( expr === old ) {
7791 			if ( anyFound == null ) {
7792 				Sizzle.error( expr );
7793 
7794 			} else {
7795 				break;
7796 			}
7797 		}
7798 
7799 		old = expr;
7800 	}
7801 
7802 	return curLoop;
7803 };
7804 
7805 Sizzle.error = function( msg ) {
7806 	throw new Error( "Syntax error, unrecognized expression: " + msg );
7807 };
7808 
7809 var getText = Sizzle.getText = function( elem ) {
7810     var i, node,
7811 		nodeType = elem.nodeType,
7812 		ret = "";
7813 
7814 	if ( nodeType ) {
7815 		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
7816 			// Use textContent || innerText for elements
7817 			if ( typeof elem.textContent === 'string' ) {
7818 				return elem.textContent;
7819 			} else if ( typeof elem.innerText === 'string' ) {
7820 				// Replace IE's carriage returns
7821 				return elem.innerText.replace( rReturn, '' );
7822 			} else {
7823 				// Traverse it's children
7824 				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
7825 					ret += getText( elem );
7826 				}
7827 			}
7828 		} else if ( nodeType === 3 || nodeType === 4 ) {
7829 			return elem.nodeValue;
7830 		}
7831 	} else {
7832 
7833 		// If no nodeType, this is expected to be an array
7834 		for ( i = 0; (node = elem[i]); i++ ) {
7835 			// Do not traverse comment nodes
7836 			if ( node.nodeType !== 8 ) {
7837 				ret += getText( node );
7838 			}
7839 		}
7840 	}
7841 	return ret;
7842 };
7843 
7844 var Expr = Sizzle.selectors = {
7845 	order: [ "ID", "NAME", "TAG" ],
7846 
7847 	match: {
7848 		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7849 		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7850 		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
7851 		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
7852 		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
7853 		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
7854 		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
7855 		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
7856 	},
7857 
7858 	leftMatch: {},
7859 
7860 	attrMap: {
7861 		"class": "className",
7862 		"for": "htmlFor"
7863 	},
7864 
7865 	attrHandle: {
7866 		href: function( elem ) {
7867 			return elem.getAttribute( "href" );
7868 		},
7869 		type: function( elem ) {
7870 			return elem.getAttribute( "type" );
7871 		}
7872 	},
7873 
7874 	relative: {
7875 		"+": function(checkSet, part){
7876 			var isPartStr = typeof part === "string",
7877 				isTag = isPartStr && !rNonWord.test( part ),
7878 				isPartStrNotTag = isPartStr && !isTag;
7879 
7880 			if ( isTag ) {
7881 				part = part.toLowerCase();
7882 			}
7883 
7884 			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
7885 				if ( (elem = checkSet[i]) ) {
7886 					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
7887 
7888 					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
7889 						elem || false :
7890 						elem === part;
7891 				}
7892 			}
7893 
7894 			if ( isPartStrNotTag ) {
7895 				Sizzle.filter( part, checkSet, true );
7896 			}
7897 		},
7898 
7899 		">": function( checkSet, part ) {
7900 			var elem,
7901 				isPartStr = typeof part === "string",
7902 				i = 0,
7903 				l = checkSet.length;
7904 
7905 			if ( isPartStr && !rNonWord.test( part ) ) {
7906 				part = part.toLowerCase();
7907 
7908 				for ( ; i < l; i++ ) {
7909 					elem = checkSet[i];
7910 
7911 					if ( elem ) {
7912 						var parent = elem.parentNode;
7913 						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
7914 					}
7915 				}
7916 
7917 			} else {
7918 				for ( ; i < l; i++ ) {
7919 					elem = checkSet[i];
7920 
7921 					if ( elem ) {
7922 						checkSet[i] = isPartStr ?
7923 							elem.parentNode :
7924 							elem.parentNode === part;
7925 					}
7926 				}
7927 
7928 				if ( isPartStr ) {
7929 					Sizzle.filter( part, checkSet, true );
7930 				}
7931 			}
7932 		},
7933 
7934 		"": function(checkSet, part, isXML){
7935 			var nodeCheck,
7936 				doneName = done++,
7937 				checkFn = dirCheck;
7938 
7939 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7940 				part = part.toLowerCase();
7941 				nodeCheck = part;
7942 				checkFn = dirNodeCheck;
7943 			}
7944 
7945 			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
7946 		},
7947 
7948 		"~": function( checkSet, part, isXML ) {
7949 			var nodeCheck,
7950 				doneName = done++,
7951 				checkFn = dirCheck;
7952 
7953 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7954 				part = part.toLowerCase();
7955 				nodeCheck = part;
7956 				checkFn = dirNodeCheck;
7957 			}
7958 
7959 			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
7960 		}
7961 	},
7962 
7963 	find: {
7964 		ID: function( match, context, isXML ) {
7965 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
7966 				var m = context.getElementById(match[1]);
7967 				// Check parentNode to catch when Blackberry 4.6 returns
7968 				// nodes that are no longer in the document #6963
7969 				return m && m.parentNode ? [m] : [];
7970 			}
7971 		},
7972 
7973 		NAME: function( match, context ) {
7974 			if ( typeof context.getElementsByName !== "undefined" ) {
7975 				var ret = [],
7976 					results = context.getElementsByName( match[1] );
7977 
7978 				for ( var i = 0, l = results.length; i < l; i++ ) {
7979 					if ( results[i].getAttribute("name") === match[1] ) {
7980 						ret.push( results[i] );
7981 					}
7982 				}
7983 
7984 				return ret.length === 0 ? null : ret;
7985 			}
7986 		},
7987 
7988 		TAG: function( match, context ) {
7989 			if ( typeof context.getElementsByTagName !== "undefined" ) {
7990 				return context.getElementsByTagName( match[1] );
7991 			}
7992 		}
7993 	},
7994 	preFilter: {
7995 		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
7996 			match = " " + match[1].replace( rBackslash, "" ) + " ";
7997 
7998 			if ( isXML ) {
7999 				return match;
8000 			}
8001 
8002 			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
8003 				if ( elem ) {
8004 					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
8005 						if ( !inplace ) {
8006 							result.push( elem );
8007 						}
8008 
8009 					} else if ( inplace ) {
8010 						curLoop[i] = false;
8011 					}
8012 				}
8013 			}
8014 
8015 			return false;
8016 		},
8017 
8018 		ID: function( match ) {
8019 			return match[1].replace( rBackslash, "" );
8020 		},
8021 
8022 		TAG: function( match, curLoop ) {
8023 			return match[1].replace( rBackslash, "" ).toLowerCase();
8024 		},
8025 
8026 		CHILD: function( match ) {
8027 			if ( match[1] === "nth" ) {
8028 				if ( !match[2] ) {
8029 					Sizzle.error( match[0] );
8030 				}
8031 
8032 				match[2] = match[2].replace(/^\+|\s*/g, '');
8033 
8034 				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
8035 				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
8036 					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
8037 					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
8038 
8039 				// calculate the numbers (first)n+(last) including if they are negative
8040 				match[2] = (test[1] + (test[2] || 1)) - 0;
8041 				match[3] = test[3] - 0;
8042 			}
8043 			else if ( match[2] ) {
8044 				Sizzle.error( match[0] );
8045 			}
8046 
8047 			// TODO: Move to normal caching system
8048 			match[0] = done++;
8049 
8050 			return match;
8051 		},
8052 
8053 		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
8054 			var name = match[1] = match[1].replace( rBackslash, "" );
8055 
8056 			if ( !isXML && Expr.attrMap[name] ) {
8057 				match[1] = Expr.attrMap[name];
8058 			}
8059 
8060 			// Handle if an un-quoted value was used
8061 			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
8062 
8063 			if ( match[2] === "~=" ) {
8064 				match[4] = " " + match[4] + " ";
8065 			}
8066 
8067 			return match;
8068 		},
8069 
8070 		PSEUDO: function( match, curLoop, inplace, result, not ) {
8071 			if ( match[1] === "not" ) {
8072 				// If we're dealing with a complex expression, or a simple one
8073 				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
8074 					match[3] = Sizzle(match[3], null, null, curLoop);
8075 
8076 				} else {
8077 					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
8078 
8079 					if ( !inplace ) {
8080 						result.push.apply( result, ret );
8081 					}
8082 
8083 					return false;
8084 				}
8085 
8086 			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
8087 				return true;
8088 			}
8089 
8090 			return match;
8091 		},
8092 
8093 		POS: function( match ) {
8094 			match.unshift( true );
8095 
8096 			return match;
8097 		}
8098 	},
8099 
8100 	filters: {
8101 		enabled: function( elem ) {
8102 			return elem.disabled === false && elem.type !== "hidden";
8103 		},
8104 
8105 		disabled: function( elem ) {
8106 			return elem.disabled === true;
8107 		},
8108 
8109 		checked: function( elem ) {
8110 			return elem.checked === true;
8111 		},
8112 
8113 		selected: function( elem ) {
8114 			// Accessing this property makes selected-by-default
8115 			// options in Safari work properly
8116 			if ( elem.parentNode ) {
8117 				elem.parentNode.selectedIndex;
8118 			}
8119 
8120 			return elem.selected === true;
8121 		},
8122 
8123 		parent: function( elem ) {
8124 			return !!elem.firstChild;
8125 		},
8126 
8127 		empty: function( elem ) {
8128 			return !elem.firstChild;
8129 		},
8130 
8131 		has: function( elem, i, match ) {
8132 			return !!Sizzle( match[3], elem ).length;
8133 		},
8134 
8135 		header: function( elem ) {
8136 			return (/h\d/i).test( elem.nodeName );
8137 		},
8138 
8139 		text: function( elem ) {
8140 			var attr = elem.getAttribute( "type" ), type = elem.type;
8141 			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
8142 			// use getAttribute instead to test this case
8143 			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
8144 		},
8145 
8146 		radio: function( elem ) {
8147 			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
8148 		},
8149 
8150 		checkbox: function( elem ) {
8151 			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
8152 		},
8153 
8154 		file: function( elem ) {
8155 			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
8156 		},
8157 
8158 		password: function( elem ) {
8159 			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
8160 		},
8161 
8162 		submit: function( elem ) {
8163 			var name = elem.nodeName.toLowerCase();
8164 			return (name === "input" || name === "button") && "submit" === elem.type;
8165 		},
8166 
8167 		image: function( elem ) {
8168 			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
8169 		},
8170 
8171 		reset: function( elem ) {
8172 			var name = elem.nodeName.toLowerCase();
8173 			return (name === "input" || name === "button") && "reset" === elem.type;
8174 		},
8175 
8176 		button: function( elem ) {
8177 			var name = elem.nodeName.toLowerCase();
8178 			return name === "input" && "button" === elem.type || name === "button";
8179 		},
8180 
8181 		input: function( elem ) {
8182 			return (/input|select|textarea|button/i).test( elem.nodeName );
8183 		},
8184 
8185 		focus: function( elem ) {
8186 			return elem === elem.ownerDocument.activeElement;
8187 		}
8188 	},
8189 	setFilters: {
8190 		first: function( elem, i ) {
8191 			return i === 0;
8192 		},
8193 
8194 		last: function( elem, i, match, array ) {
8195 			return i === array.length - 1;
8196 		},
8197 
8198 		even: function( elem, i ) {
8199 			return i % 2 === 0;
8200 		},
8201 
8202 		odd: function( elem, i ) {
8203 			return i % 2 === 1;
8204 		},
8205 
8206 		lt: function( elem, i, match ) {
8207 			return i < match[3] - 0;
8208 		},
8209 
8210 		gt: function( elem, i, match ) {
8211 			return i > match[3] - 0;
8212 		},
8213 
8214 		nth: function( elem, i, match ) {
8215 			return match[3] - 0 === i;
8216 		},
8217 
8218 		eq: function( elem, i, match ) {
8219 			return match[3] - 0 === i;
8220 		}
8221 	},
8222 	filter: {
8223 		PSEUDO: function( elem, match, i, array ) {
8224 			var name = match[1],
8225 				filter = Expr.filters[ name ];
8226 
8227 			if ( filter ) {
8228 				return filter( elem, i, match, array );
8229 
8230 			} else if ( name === "contains" ) {
8231 				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
8232 
8233 			} else if ( name === "not" ) {
8234 				var not = match[3];
8235 
8236 				for ( var j = 0, l = not.length; j < l; j++ ) {
8237 					if ( not[j] === elem ) {
8238 						return false;
8239 					}
8240 				}
8241 
8242 				return true;
8243 
8244 			} else {
8245 				Sizzle.error( name );
8246 			}
8247 		},
8248 
8249 		CHILD: function( elem, match ) {
8250 			var first, last,
8251 				doneName, parent, cache,
8252 				count, diff,
8253 				type = match[1],
8254 				node = elem;
8255 
8256 			switch ( type ) {
8257 				case "only":
8258 				case "first":
8259 					while ( (node = node.previousSibling) ) {
8260 						if ( node.nodeType === 1 ) {
8261 							return false;
8262 						}
8263 					}
8264 
8265 					if ( type === "first" ) {
8266 						return true;
8267 					}
8268 
8269 					node = elem;
8270 
8271 					/* falls through */
8272 				case "last":
8273 					while ( (node = node.nextSibling) ) {
8274 						if ( node.nodeType === 1 ) {
8275 							return false;
8276 						}
8277 					}
8278 
8279 					return true;
8280 
8281 				case "nth":
8282 					first = match[2];
8283 					last = match[3];
8284 
8285 					if ( first === 1 && last === 0 ) {
8286 						return true;
8287 					}
8288 
8289 					doneName = match[0];
8290 					parent = elem.parentNode;
8291 
8292 					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
8293 						count = 0;
8294 
8295 						for ( node = parent.firstChild; node; node = node.nextSibling ) {
8296 							if ( node.nodeType === 1 ) {
8297 								node.nodeIndex = ++count;
8298 							}
8299 						}
8300 
8301 						parent[ expando ] = doneName;
8302 					}
8303 
8304 					diff = elem.nodeIndex - last;
8305 
8306 					if ( first === 0 ) {
8307 						return diff === 0;
8308 
8309 					} else {
8310 						return ( diff % first === 0 && diff / first >= 0 );
8311 					}
8312 			}
8313 		},
8314 
8315 		ID: function( elem, match ) {
8316 			return elem.nodeType === 1 && elem.getAttribute("id") === match;
8317 		},
8318 
8319 		TAG: function( elem, match ) {
8320 			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
8321 		},
8322 
8323 		CLASS: function( elem, match ) {
8324 			return (" " + (elem.className || elem.getAttribute("class")) + " ")
8325 				.indexOf( match ) > -1;
8326 		},
8327 
8328 		ATTR: function( elem, match ) {
8329 			var name = match[1],
8330 				result = Sizzle.attr ?
8331 					Sizzle.attr( elem, name ) :
8332 					Expr.attrHandle[ name ] ?
8333 					Expr.attrHandle[ name ]( elem ) :
8334 					elem[ name ] != null ?
8335 						elem[ name ] :
8336 						elem.getAttribute( name ),
8337 				value = result + "",
8338 				type = match[2],
8339 				check = match[4];
8340 
8341 			return result == null ?
8342 				type === "!=" :
8343 				!type && Sizzle.attr ?
8344 				result != null :
8345 				type === "=" ?
8346 				value === check :
8347 				type === "*=" ?
8348 				value.indexOf(check) >= 0 :
8349 				type === "~=" ?
8350 				(" " + value + " ").indexOf(check) >= 0 :
8351 				!check ?
8352 				value && result !== false :
8353 				type === "!=" ?
8354 				value !== check :
8355 				type === "^=" ?
8356 				value.indexOf(check) === 0 :
8357 				type === "$=" ?
8358 				value.substr(value.length - check.length) === check :
8359 				type === "|=" ?
8360 				value === check || value.substr(0, check.length + 1) === check + "-" :
8361 				false;
8362 		},
8363 
8364 		POS: function( elem, match, i, array ) {
8365 			var name = match[2],
8366 				filter = Expr.setFilters[ name ];
8367 
8368 			if ( filter ) {
8369 				return filter( elem, i, match, array );
8370 			}
8371 		}
8372 	}
8373 };
8374 
8375 var origPOS = Expr.match.POS,
8376 	fescape = function(all, num){
8377 		return "\\" + (num - 0 + 1);
8378 	};
8379 
8380 for ( var type in Expr.match ) {
8381 	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
8382 	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
8383 }
8384 // Expose origPOS
8385 // "global" as in regardless of relation to brackets/parens
8386 Expr.match.globalPOS = origPOS;
8387 
8388 var makeArray = function( array, results ) {
8389 	array = Array.prototype.slice.call( array, 0 );
8390 
8391 	if ( results ) {
8392 		results.push.apply( results, array );
8393 		return results;
8394 	}
8395 
8396 	return array;
8397 };
8398 
8399 // Perform a simple check to determine if the browser is capable of
8400 // converting a NodeList to an array using builtin methods.
8401 // Also verifies that the returned array holds DOM nodes
8402 // (which is not the case in the Blackberry browser)
8403 try {
8404 	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
8405 
8406 // Provide a fallback method if it does not work
8407 } catch( e ) {
8408 	makeArray = function( array, results ) {
8409 		var i = 0,
8410 			ret = results || [];
8411 
8412 		if ( toString.call(array) === "[object Array]" ) {
8413 			Array.prototype.push.apply( ret, array );
8414 
8415 		} else {
8416 			if ( typeof array.length === "number" ) {
8417 				for ( var l = array.length; i < l; i++ ) {
8418 					ret.push( array[i] );
8419 				}
8420 
8421 			} else {
8422 				for ( ; array[i]; i++ ) {
8423 					ret.push( array[i] );
8424 				}
8425 			}
8426 		}
8427 
8428 		return ret;
8429 	};
8430 }
8431 
8432 var sortOrder, siblingCheck;
8433 
8434 if ( document.documentElement.compareDocumentPosition ) {
8435 	sortOrder = function( a, b ) {
8436 		if ( a === b ) {
8437 			hasDuplicate = true;
8438 			return 0;
8439 		}
8440 
8441 		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
8442 			return a.compareDocumentPosition ? -1 : 1;
8443 		}
8444 
8445 		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
8446 	};
8447 
8448 } else {
8449 	sortOrder = function( a, b ) {
8450 		// The nodes are identical, we can exit early
8451 		if ( a === b ) {
8452 			hasDuplicate = true;
8453 			return 0;
8454 
8455 		// Fallback to using sourceIndex (in IE) if it's available on both nodes
8456 		} else if ( a.sourceIndex && b.sourceIndex ) {
8457 			return a.sourceIndex - b.sourceIndex;
8458 		}
8459 
8460 		var al, bl,
8461 			ap = [],
8462 			bp = [],
8463 			aup = a.parentNode,
8464 			bup = b.parentNode,
8465 			cur = aup;
8466 
8467 		// If the nodes are siblings (or identical) we can do a quick check
8468 		if ( aup === bup ) {
8469 			return siblingCheck( a, b );
8470 
8471 		// If no parents were found then the nodes are disconnected
8472 		} else if ( !aup ) {
8473 			return -1;
8474 
8475 		} else if ( !bup ) {
8476 			return 1;
8477 		}
8478 
8479 		// Otherwise they're somewhere else in the tree so we need
8480 		// to build up a full list of the parentNodes for comparison
8481 		while ( cur ) {
8482 			ap.unshift( cur );
8483 			cur = cur.parentNode;
8484 		}
8485 
8486 		cur = bup;
8487 
8488 		while ( cur ) {
8489 			bp.unshift( cur );
8490 			cur = cur.parentNode;
8491 		}
8492 
8493 		al = ap.length;
8494 		bl = bp.length;
8495 
8496 		// Start walking down the tree looking for a discrepancy
8497 		for ( var i = 0; i < al && i < bl; i++ ) {
8498 			if ( ap[i] !== bp[i] ) {
8499 				return siblingCheck( ap[i], bp[i] );
8500 			}
8501 		}
8502 
8503 		// We ended someplace up the tree so do a sibling check
8504 		return i === al ?
8505 			siblingCheck( a, bp[i], -1 ) :
8506 			siblingCheck( ap[i], b, 1 );
8507 	};
8508 
8509 	siblingCheck = function( a, b, ret ) {
8510 		if ( a === b ) {
8511 			return ret;
8512 		}
8513 
8514 		var cur = a.nextSibling;
8515 
8516 		while ( cur ) {
8517 			if ( cur === b ) {
8518 				return -1;
8519 			}
8520 
8521 			cur = cur.nextSibling;
8522 		}
8523 
8524 		return 1;
8525 	};
8526 }
8527 
8528 // Check to see if the browser returns elements by name when
8529 // querying by getElementById (and provide a workaround)
8530 (function(){
8531 	// We're going to inject a fake input element with a specified name
8532 	var form = document.createElement("div"),
8533 		id = "script" + (new Date()).getTime(),
8534 		root = document.documentElement;
8535 
8536 	form.innerHTML = "<a name='" + id + "'/>";
8537 
8538 	// Inject it into the root element, check its status, and remove it quickly
8539 	root.insertBefore( form, root.firstChild );
8540 
8541 	// The workaround has to do additional checks after a getElementById
8542 	// Which slows things down for other browsers (hence the branching)
8543 	if ( document.getElementById( id ) ) {
8544 		Expr.find.ID = function( match, context, isXML ) {
8545 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
8546 				var m = context.getElementById(match[1]);
8547 
8548 				return m ?
8549 					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
8550 						[m] :
8551 						undefined :
8552 					[];
8553 			}
8554 		};
8555 
8556 		Expr.filter.ID = function( elem, match ) {
8557 			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
8558 
8559 			return elem.nodeType === 1 && node && node.nodeValue === match;
8560 		};
8561 	}
8562 
8563 	root.removeChild( form );
8564 
8565 	// release memory in IE
8566 	root = form = null;
8567 })();
8568 
8569 (function(){
8570 	// Check to see if the browser returns only elements
8571 	// when doing getElementsByTagName("*")
8572 
8573 	// Create a fake element
8574 	var div = document.createElement("div");
8575 	div.appendChild( document.createComment("") );
8576 
8577 	// Make sure no comments are found
8578 	if ( div.getElementsByTagName("*").length > 0 ) {
8579 		Expr.find.TAG = function( match, context ) {
8580 			var results = context.getElementsByTagName( match[1] );
8581 
8582 			// Filter out possible comments
8583 			if ( match[1] === "*" ) {
8584 				var tmp = [];
8585 
8586 				for ( var i = 0; results[i]; i++ ) {
8587 					if ( results[i].nodeType === 1 ) {
8588 						tmp.push( results[i] );
8589 					}
8590 				}
8591 
8592 				results = tmp;
8593 			}
8594 
8595 			return results;
8596 		};
8597 	}
8598 
8599 	// Check to see if an attribute returns normalized href attributes
8600 	div.innerHTML = "<a href='#'></a>";
8601 
8602 	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
8603 			div.firstChild.getAttribute("href") !== "#" ) {
8604 
8605 		Expr.attrHandle.href = function( elem ) {
8606 			return elem.getAttribute( "href", 2 );
8607 		};
8608 	}
8609 
8610 	// release memory in IE
8611 	div = null;
8612 })();
8613 
8614 if ( document.querySelectorAll ) {
8615 	(function(){
8616 		var oldSizzle = Sizzle,
8617 			div = document.createElement("div"),
8618 			id = "__sizzle__";
8619 
8620 		div.innerHTML = "<p class='TEST'></p>";
8621 
8622 		// Safari can't handle uppercase or unicode characters when
8623 		// in quirks mode.
8624 		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
8625 			return;
8626 		}
8627 
8628 		Sizzle = function( query, context, extra, seed ) {
8629 			context = context || document;
8630 
8631 			// Only use querySelectorAll on non-XML documents
8632 			// (ID selectors don't work in non-HTML documents)
8633 			if ( !seed && !Sizzle.isXML(context) ) {
8634 				// See if we find a selector to speed up
8635 				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
8636 
8637 				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
8638 					// Speed-up: Sizzle("TAG")
8639 					if ( match[1] ) {
8640 						return makeArray( context.getElementsByTagName( query ), extra );
8641 
8642 					// Speed-up: Sizzle(".CLASS")
8643 					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
8644 						return makeArray( context.getElementsByClassName( match[2] ), extra );
8645 					}
8646 				}
8647 
8648 				if ( context.nodeType === 9 ) {
8649 					// Speed-up: Sizzle("body")
8650 					// The body element only exists once, optimize finding it
8651 					if ( query === "body" && context.body ) {
8652 						return makeArray( [ context.body ], extra );
8653 
8654 					// Speed-up: Sizzle("#ID")
8655 					} else if ( match && match[3] ) {
8656 						var elem = context.getElementById( match[3] );
8657 
8658 						// Check parentNode to catch when Blackberry 4.6 returns
8659 						// nodes that are no longer in the document #6963
8660 						if ( elem && elem.parentNode ) {
8661 							// Handle the case where IE and Opera return items
8662 							// by name instead of ID
8663 							if ( elem.id === match[3] ) {
8664 								return makeArray( [ elem ], extra );
8665 							}
8666 
8667 						} else {
8668 							return makeArray( [], extra );
8669 						}
8670 					}
8671 
8672 					try {
8673 						return makeArray( context.querySelectorAll(query), extra );
8674 					} catch(qsaError) {}
8675 
8676 				// qSA works strangely on Element-rooted queries
8677 				// We can work around this by specifying an extra ID on the root
8678 				// and working up from there (Thanks to Andrew Dupont for the technique)
8679 				// IE 8 doesn't work on object elements
8680 				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
8681 					var oldContext = context,
8682 						old = context.getAttribute( "id" ),
8683 						nid = old || id,
8684 						hasParent = context.parentNode,
8685 						relativeHierarchySelector = /^\s*[+~]/.test( query );
8686 
8687 					if ( !old ) {
8688 						context.setAttribute( "id", nid );
8689 					} else {
8690 						nid = nid.replace( /'/g, "\\$&" );
8691 					}
8692 					if ( relativeHierarchySelector && hasParent ) {
8693 						context = context.parentNode;
8694 					}
8695 
8696 					try {
8697 						if ( !relativeHierarchySelector || hasParent ) {
8698 							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
8699 						}
8700 
8701 					} catch(pseudoError) {
8702 					} finally {
8703 						if ( !old ) {
8704 							oldContext.removeAttribute( "id" );
8705 						}
8706 					}
8707 				}
8708 			}
8709 
8710 			return oldSizzle(query, context, extra, seed);
8711 		};
8712 
8713 		for ( var prop in oldSizzle ) {
8714 			Sizzle[ prop ] = oldSizzle[ prop ];
8715 		}
8716 
8717 		// release memory in IE
8718 		div = null;
8719 	})();
8720 }
8721 
8722 (function(){
8723 	var html = document.documentElement,
8724 		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
8725 
8726 	if ( matches ) {
8727 		// Check to see if it's possible to do matchesSelector
8728 		// on a disconnected node (IE 9 fails this)
8729 		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
8730 			pseudoWorks = false;
8731 
8732 		try {
8733 			// This should fail with an exception
8734 			// Gecko does not error, returns false instead
8735 			matches.call( document.documentElement, "[test!='']:sizzle" );
8736 
8737 		} catch( pseudoError ) {
8738 			pseudoWorks = true;
8739 		}
8740 
8741 		Sizzle.matchesSelector = function( node, expr ) {
8742 			// Make sure that attribute selectors are quoted
8743 			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
8744 
8745 			if ( !Sizzle.isXML( node ) ) {
8746 				try {
8747 					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
8748 						var ret = matches.call( node, expr );
8749 
8750 						// IE 9's matchesSelector returns false on disconnected nodes
8751 						if ( ret || !disconnectedMatch ||
8752 								// As well, disconnected nodes are said to be in a document
8753 								// fragment in IE 9, so check for that
8754 								node.document && node.document.nodeType !== 11 ) {
8755 							return ret;
8756 						}
8757 					}
8758 				} catch(e) {}
8759 			}
8760 
8761 			return Sizzle(expr, null, null, [node]).length > 0;
8762 		};
8763 	}
8764 })();
8765 
8766 (function(){
8767 	var div = document.createElement("div");
8768 
8769 	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
8770 
8771 	// Opera can't find a second classname (in 9.6)
8772 	// Also, make sure that getElementsByClassName actually exists
8773 	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
8774 		return;
8775 	}
8776 
8777 	// Safari caches class attributes, doesn't catch changes (in 3.2)
8778 	div.lastChild.className = "e";
8779 
8780 	if ( div.getElementsByClassName("e").length === 1 ) {
8781 		return;
8782 	}
8783 
8784 	Expr.order.splice(1, 0, "CLASS");
8785 	Expr.find.CLASS = function( match, context, isXML ) {
8786 		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
8787 			return context.getElementsByClassName(match[1]);
8788 		}
8789 	};
8790 
8791 	// release memory in IE
8792 	div = null;
8793 })();
8794 
8795 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8796 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8797 		var elem = checkSet[i];
8798 
8799 		if ( elem ) {
8800 			var match = false;
8801 
8802 			elem = elem[dir];
8803 
8804 			while ( elem ) {
8805 				if ( elem[ expando ] === doneName ) {
8806 					match = checkSet[elem.sizset];
8807 					break;
8808 				}
8809 
8810 				if ( elem.nodeType === 1 && !isXML ){
8811 					elem[ expando ] = doneName;
8812 					elem.sizset = i;
8813 				}
8814 
8815 				if ( elem.nodeName.toLowerCase() === cur ) {
8816 					match = elem;
8817 					break;
8818 				}
8819 
8820 				elem = elem[dir];
8821 			}
8822 
8823 			checkSet[i] = match;
8824 		}
8825 	}
8826 }
8827 
8828 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8829 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8830 		var elem = checkSet[i];
8831 
8832 		if ( elem ) {
8833 			var match = false;
8834 
8835 			elem = elem[dir];
8836 
8837 			while ( elem ) {
8838 				if ( elem[ expando ] === doneName ) {
8839 					match = checkSet[elem.sizset];
8840 					break;
8841 				}
8842 
8843 				if ( elem.nodeType === 1 ) {
8844 					if ( !isXML ) {
8845 						elem[ expando ] = doneName;
8846 						elem.sizset = i;
8847 					}
8848 
8849 					if ( typeof cur !== "string" ) {
8850 						if ( elem === cur ) {
8851 							match = true;
8852 							break;
8853 						}
8854 
8855 					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
8856 						match = elem;
8857 						break;
8858 					}
8859 				}
8860 
8861 				elem = elem[dir];
8862 			}
8863 
8864 			checkSet[i] = match;
8865 		}
8866 	}
8867 }
8868 
8869 if ( document.documentElement.contains ) {
8870 	Sizzle.contains = function( a, b ) {
8871 		return a !== b && (a.contains ? a.contains(b) : true);
8872 	};
8873 
8874 } else if ( document.documentElement.compareDocumentPosition ) {
8875 	Sizzle.contains = function( a, b ) {
8876 		return !!(a.compareDocumentPosition(b) & 16);
8877 	};
8878 
8879 } else {
8880 	Sizzle.contains = function() {
8881 		return false;
8882 	};
8883 }
8884 
8885 Sizzle.isXML = function( elem ) {
8886 	// documentElement is verified for cases where it doesn't yet exist
8887 	// (such as loading iframes in IE - #4833)
8888 	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
8889 
8890 	return documentElement ? documentElement.nodeName !== "HTML" : false;
8891 };
8892 
8893 var posProcess = function( selector, context, seed ) {
8894 	var match,
8895 		tmpSet = [],
8896 		later = "",
8897 		root = context.nodeType ? [context] : context;
8898 
8899 	// Position selectors must be done after the filter
8900 	// And so must :not(positional) so we move all PSEUDOs to the end
8901 	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
8902 		later += match[0];
8903 		selector = selector.replace( Expr.match.PSEUDO, "" );
8904 	}
8905 
8906 	selector = Expr.relative[selector] ? selector + "*" : selector;
8907 
8908 	for ( var i = 0, l = root.length; i < l; i++ ) {
8909 		Sizzle( selector, root[i], tmpSet, seed );
8910 	}
8911 
8912 	return Sizzle.filter( later, tmpSet );
8913 };
8914 
8915 // EXPOSE
8916 
8917 window.tinymce.dom.Sizzle = Sizzle;
8918 
8919 })();
8920 
8921 
8922 (function(tinymce) {
8923 	tinymce.dom.Element = function(id, settings) {
8924 		var t = this, dom, el;
8925 
8926 		t.settings = settings = settings || {};
8927 		t.id = id;
8928 		t.dom = dom = settings.dom || tinymce.DOM;
8929 
8930 		// Only IE leaks DOM references, this is a lot faster
8931 		if (!tinymce.isIE)
8932 			el = dom.get(t.id);
8933 
8934 		tinymce.each(
8935 				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
8936 				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
8937 				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
8938 				'isHidden,setHTML,get').split(/,/), function(k) {
8939 					t[k] = function() {
8940 						var a = [id], i;
8941 
8942 						for (i = 0; i < arguments.length; i++)
8943 							a.push(arguments[i]);
8944 
8945 						a = dom[k].apply(dom, a);
8946 						t.update(k);
8947 
8948 						return a;
8949 					};
8950 			}
8951 		);
8952 
8953 		tinymce.extend(t, {
8954 			on : function(n, f, s) {
8955 				return tinymce.dom.Event.add(t.id, n, f, s);
8956 			},
8957 
8958 			getXY : function() {
8959 				return {
8960 					x : parseInt(t.getStyle('left')),
8961 					y : parseInt(t.getStyle('top'))
8962 				};
8963 			},
8964 
8965 			getSize : function() {
8966 				var n = dom.get(t.id);
8967 
8968 				return {
8969 					w : parseInt(t.getStyle('width') || n.clientWidth),
8970 					h : parseInt(t.getStyle('height') || n.clientHeight)
8971 				};
8972 			},
8973 
8974 			moveTo : function(x, y) {
8975 				t.setStyles({left : x, top : y});
8976 			},
8977 
8978 			moveBy : function(x, y) {
8979 				var p = t.getXY();
8980 
8981 				t.moveTo(p.x + x, p.y + y);
8982 			},
8983 
8984 			resizeTo : function(w, h) {
8985 				t.setStyles({width : w, height : h});
8986 			},
8987 
8988 			resizeBy : function(w, h) {
8989 				var s = t.getSize();
8990 
8991 				t.resizeTo(s.w + w, s.h + h);
8992 			},
8993 
8994 			update : function(k) {
8995 				var b;
8996 
8997 				if (tinymce.isIE6 && settings.blocker) {
8998 					k = k || '';
8999 
9000 					// Ignore getters
9001 					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
9002 						return;
9003 
9004 					// Remove blocker on remove
9005 					if (k == 'remove') {
9006 						dom.remove(t.blocker);
9007 						return;
9008 					}
9009 
9010 					if (!t.blocker) {
9011 						t.blocker = dom.uniqueId();
9012 						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
9013 						dom.setStyle(b, 'opacity', 0);
9014 					} else
9015 						b = dom.get(t.blocker);
9016 
9017 					dom.setStyles(b, {
9018 						left : t.getStyle('left', 1),
9019 						top : t.getStyle('top', 1),
9020 						width : t.getStyle('width', 1),
9021 						height : t.getStyle('height', 1),
9022 						display : t.getStyle('display', 1),
9023 						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
9024 					});
9025 				}
9026 			}
9027 		});
9028 	};
9029 })(tinymce);
9030 
9031 (function(tinymce) {
9032 	function trimNl(s) {
9033 		return s.replace(/[\n\r]+/g, '');
9034 	};
9035 
9036 	// Shorten names
9037 	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
9038 
9039 	tinymce.create('tinymce.dom.Selection', {
9040 		Selection : function(dom, win, serializer, editor) {
9041 			var t = this;
9042 
9043 			t.dom = dom;
9044 			t.win = win;
9045 			t.serializer = serializer;
9046 			t.editor = editor;
9047 
9048 			// Add events
9049 			each([
9050 				'onBeforeSetContent',
9051 
9052 				'onBeforeGetContent',
9053 
9054 				'onSetContent',
9055 
9056 				'onGetContent'
9057 			], function(e) {
9058 				t[e] = new tinymce.util.Dispatcher(t);
9059 			});
9060 
9061 			// No W3C Range support
9062 			if (!t.win.getSelection)
9063 				t.tridentSel = new tinymce.dom.TridentSelection(t);
9064 
9065 			if (tinymce.isIE && dom.boxModel)
9066 				this._fixIESelection();
9067 
9068 			// Prevent leaks
9069 			tinymce.addUnload(t.destroy, t);
9070 		},
9071 
9072 		setCursorLocation: function(node, offset) {
9073 			var t = this; var r = t.dom.createRng();
9074 			r.setStart(node, offset);
9075 			r.setEnd(node, offset);
9076 			t.setRng(r);
9077 			t.collapse(false);
9078 		},
9079 		getContent : function(s) {
9080 			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
9081 
9082 			s = s || {};
9083 			wb = wa = '';
9084 			s.get = true;
9085 			s.format = s.format || 'html';
9086 			s.forced_root_block = '';
9087 			t.onBeforeGetContent.dispatch(t, s);
9088 
9089 			if (s.format == 'text')
9090 				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
9091 
9092 			if (r.cloneContents) {
9093 				n = r.cloneContents();
9094 
9095 				if (n)
9096 					e.appendChild(n);
9097 			} else if (is(r.item) || is(r.htmlText)) {
9098 				// IE will produce invalid markup if elements are present that
9099 				// it doesn't understand like custom elements or HTML5 elements.
9100 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
9101 				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
9102 				e.removeChild(e.firstChild);
9103 			} else
9104 				e.innerHTML = r.toString();
9105 
9106 			// Keep whitespace before and after
9107 			if (/^\s/.test(e.innerHTML))
9108 				wb = ' ';
9109 
9110 			if (/\s+$/.test(e.innerHTML))
9111 				wa = ' ';
9112 
9113 			s.getInner = true;
9114 
9115 			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
9116 			t.onGetContent.dispatch(t, s);
9117 
9118 			return s.content;
9119 		},
9120 
9121 		setContent : function(content, args) {
9122 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9123 
9124 			args = args || {format : 'html'};
9125 			args.set = true;
9126 			content = args.content = content;
9127 
9128 			// Dispatch before set content event
9129 			if (!args.no_events)
9130 				self.onBeforeSetContent.dispatch(self, args);
9131 
9132 			content = args.content;
9133 
9134 			if (rng.insertNode) {
9135 				// Make caret marker since insertNode places the caret in the beginning of text after insert
9136 				content += '<span id="__caret">_</span>';
9137 
9138 				// Delete and insert new node
9139 				if (rng.startContainer == doc && rng.endContainer == doc) {
9140 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9141 					doc.body.innerHTML = content;
9142 				} else {
9143 					rng.deleteContents();
9144 
9145 					if (doc.body.childNodes.length === 0) {
9146 						doc.body.innerHTML = content;
9147 					} else {
9148 						// createContextualFragment doesn't exists in IE 9 DOMRanges
9149 						if (rng.createContextualFragment) {
9150 							rng.insertNode(rng.createContextualFragment(content));
9151 						} else {
9152 							// Fake createContextualFragment call in IE 9
9153 							frag = doc.createDocumentFragment();
9154 							temp = doc.createElement('div');
9155 
9156 							frag.appendChild(temp);
9157 							temp.outerHTML = content;
9158 
9159 							rng.insertNode(frag);
9160 						}
9161 					}
9162 				}
9163 
9164 				// Move to caret marker
9165 				caretNode = self.dom.get('__caret');
9166 
9167 				// Make sure we wrap it compleatly, Opera fails with a simple select call
9168 				rng = doc.createRange();
9169 				rng.setStartBefore(caretNode);
9170 				rng.setEndBefore(caretNode);
9171 				self.setRng(rng);
9172 
9173 				// Remove the caret position
9174 				self.dom.remove('__caret');
9175 
9176 				try {
9177 					self.setRng(rng);
9178 				} catch (ex) {
9179 					// Might fail on Opera for some odd reason
9180 				}
9181 			} else {
9182 				if (rng.item) {
9183 					// Delete content and get caret text selection
9184 					doc.execCommand('Delete', false, null);
9185 					rng = self.getRng();
9186 				}
9187 
9188 				// Explorer removes spaces from the beginning of pasted contents
9189 				if (/^\s+/.test(content)) {
9190 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9191 					self.dom.remove('__mce_tmp');
9192 				} else
9193 					rng.pasteHTML(content);
9194 			}
9195 
9196 			// Dispatch set content event
9197 			if (!args.no_events)
9198 				self.onSetContent.dispatch(self, args);
9199 		},
9200 
9201 		getStart : function() {
9202 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9203 
9204 			if (rng.duplicate || rng.item) {
9205 				// Control selection, return first item
9206 				if (rng.item)
9207 					return rng.item(0);
9208 
9209 				// Get start element
9210 				checkRng = rng.duplicate();
9211 				checkRng.collapse(1);
9212 				startElement = checkRng.parentElement();
9213 				if (startElement.ownerDocument !== self.dom.doc) {
9214 					startElement = self.dom.getRoot();
9215 				}
9216 
9217 				// Check if range parent is inside the start element, then return the inner parent element
9218 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9219 				parentElement = node = rng.parentElement();
9220 				while (node = node.parentNode) {
9221 					if (node == startElement) {
9222 						startElement = parentElement;
9223 						break;
9224 					}
9225 				}
9226 
9227 				return startElement;
9228 			} else {
9229 				startElement = rng.startContainer;
9230 
9231 				if (startElement.nodeType == 1 && startElement.hasChildNodes())
9232 					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9233 
9234 				if (startElement && startElement.nodeType == 3)
9235 					return startElement.parentNode;
9236 
9237 				return startElement;
9238 			}
9239 		},
9240 
9241 		getEnd : function() {
9242 			var self = this, rng = self.getRng(), endElement, endOffset;
9243 
9244 			if (rng.duplicate || rng.item) {
9245 				if (rng.item)
9246 					return rng.item(0);
9247 
9248 				rng = rng.duplicate();
9249 				rng.collapse(0);
9250 				endElement = rng.parentElement();
9251 				if (endElement.ownerDocument !== self.dom.doc) {
9252 					endElement = self.dom.getRoot();
9253 				}
9254 
9255 				if (endElement && endElement.nodeName == 'BODY')
9256 					return endElement.lastChild || endElement;
9257 
9258 				return endElement;
9259 			} else {
9260 				endElement = rng.endContainer;
9261 				endOffset = rng.endOffset;
9262 
9263 				if (endElement.nodeType == 1 && endElement.hasChildNodes())
9264 					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9265 
9266 				if (endElement && endElement.nodeType == 3)
9267 					return endElement.parentNode;
9268 
9269 				return endElement;
9270 			}
9271 		},
9272 
9273 		getBookmark : function(type, normalized) {
9274 			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
9275 
9276 			function findIndex(name, element) {
9277 				var index = 0;
9278 
9279 				each(dom.select(name), function(node, i) {
9280 					if (node == element)
9281 						index = i;
9282 				});
9283 
9284 				return index;
9285 			};
9286 
9287 			function normalizeTableCellSelection(rng) {
9288 				function moveEndPoint(start) {
9289 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
9290 
9291 					container = rng[prefix + 'Container'];
9292 					offset = rng[prefix + 'Offset'];
9293 
9294 					if (container.nodeType == 1 && container.nodeName == "TR") {
9295 						childNodes = container.childNodes;
9296 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9297 						if (container) {
9298 							offset = start ? 0 : container.childNodes.length;
9299 							rng['set' + (start ? 'Start' : 'End')](container, offset);
9300 						}
9301 					}
9302 				};
9303 
9304 				moveEndPoint(true);
9305 				moveEndPoint();
9306 
9307 				return rng;
9308 			};
9309 
9310 			function getLocation() {
9311 				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9312 
9313 				function getPoint(rng, start) {
9314 					var container = rng[start ? 'startContainer' : 'endContainer'],
9315 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9316 
9317 					if (container.nodeType == 3) {
9318 						if (normalized) {
9319 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
9320 								offset += node.nodeValue.length;
9321 						}
9322 
9323 						point.push(offset);
9324 					} else {
9325 						childNodes = container.childNodes;
9326 
9327 						if (offset >= childNodes.length && childNodes.length) {
9328 							after = 1;
9329 							offset = Math.max(0, childNodes.length - 1);
9330 						}
9331 
9332 						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
9333 					}
9334 
9335 					for (; container && container != root; container = container.parentNode)
9336 						point.push(t.dom.nodeIndex(container, normalized));
9337 
9338 					return point;
9339 				};
9340 
9341 				bookmark.start = getPoint(rng, true);
9342 
9343 				if (!t.isCollapsed())
9344 					bookmark.end = getPoint(rng);
9345 
9346 				return bookmark;
9347 			};
9348 
9349 			if (type == 2) {
9350 				if (t.tridentSel)
9351 					return t.tridentSel.getBookmark(type);
9352 
9353 				return getLocation();
9354 			}
9355 
9356 			// Handle simple range
9357 			if (type)
9358 				return {rng : t.getRng()};
9359 
9360 			rng = t.getRng();
9361 			id = dom.uniqueId();
9362 			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
9363 			styles = 'overflow:hidden;line-height:0px';
9364 
9365 			// Explorer method
9366 			if (rng.duplicate || rng.item) {
9367 				// Text selection
9368 				if (!rng.item) {
9369 					rng2 = rng.duplicate();
9370 
9371 					try {
9372 						// Insert start marker
9373 						rng.collapse();
9374 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
9375 
9376 						// Insert end marker
9377 						if (!collapsed) {
9378 							rng2.collapse(false);
9379 
9380 							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
9381 							rng.moveToElementText(rng2.parentElement());
9382 							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
9383 								rng2.move('character', -1);
9384 
9385 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
9386 						}
9387 					} catch (ex) {
9388 						// IE might throw unspecified error so lets ignore it
9389 						return null;
9390 					}
9391 				} else {
9392 					// Control selection
9393 					element = rng.item(0);
9394 					name = element.nodeName;
9395 
9396 					return {name : name, index : findIndex(name, element)};
9397 				}
9398 			} else {
9399 				element = t.getNode();
9400 				name = element.nodeName;
9401 				if (name == 'IMG')
9402 					return {name : name, index : findIndex(name, element)};
9403 
9404 				// W3C method
9405 				rng2 = normalizeTableCellSelection(rng.cloneRange());
9406 
9407 				// Insert end marker
9408 				if (!collapsed) {
9409 					rng2.collapse(false);
9410 					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9411 				}
9412 
9413 				rng = normalizeTableCellSelection(rng);
9414 				rng.collapse(true);
9415 				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9416 			}
9417 
9418 			t.moveToBookmark({id : id, keep : 1});
9419 
9420 			return {id : id};
9421 		},
9422 
9423 		moveToBookmark : function(bookmark) {
9424 			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
9425 
9426 			function setEndPoint(start) {
9427 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
9428 
9429 				if (point) {
9430 					offset = point[0];
9431 
9432 					// Find container node
9433 					for (node = root, i = point.length - 1; i >= 1; i--) {
9434 						children = node.childNodes;
9435 
9436 						if (point[i] > children.length - 1)
9437 							return;
9438 
9439 						node = children[point[i]];
9440 					}
9441 
9442 					// Move text offset to best suitable location
9443 					if (node.nodeType === 3)
9444 						offset = Math.min(point[0], node.nodeValue.length);
9445 
9446 					// Move element offset to best suitable location
9447 					if (node.nodeType === 1)
9448 						offset = Math.min(point[0], node.childNodes.length);
9449 
9450 					// Set offset within container node
9451 					if (start)
9452 						rng.setStart(node, offset);
9453 					else
9454 						rng.setEnd(node, offset);
9455 				}
9456 
9457 				return true;
9458 			};
9459 
9460 			function restoreEndPoint(suffix) {
9461 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
9462 
9463 				if (marker) {
9464 					node = marker.parentNode;
9465 
9466 					if (suffix == 'start') {
9467 						if (!keep) {
9468 							idx = dom.nodeIndex(marker);
9469 						} else {
9470 							node = marker.firstChild;
9471 							idx = 1;
9472 						}
9473 
9474 						startContainer = endContainer = node;
9475 						startOffset = endOffset = idx;
9476 					} else {
9477 						if (!keep) {
9478 							idx = dom.nodeIndex(marker);
9479 						} else {
9480 							node = marker.firstChild;
9481 							idx = 1;
9482 						}
9483 
9484 						endContainer = node;
9485 						endOffset = idx;
9486 					}
9487 
9488 					if (!keep) {
9489 						prev = marker.previousSibling;
9490 						next = marker.nextSibling;
9491 
9492 						// Remove all marker text nodes
9493 						each(tinymce.grep(marker.childNodes), function(node) {
9494 							if (node.nodeType == 3)
9495 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
9496 						});
9497 
9498 						// Remove marker but keep children if for example contents where inserted into the marker
9499 						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
9500 						while (marker = dom.get(bookmark.id + '_' + suffix))
9501 							dom.remove(marker, 1);
9502 
9503 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
9504 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
9505 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
9506 							idx = prev.nodeValue.length;
9507 							prev.appendData(next.nodeValue);
9508 							dom.remove(next);
9509 
9510 							if (suffix == 'start') {
9511 								startContainer = endContainer = prev;
9512 								startOffset = endOffset = idx;
9513 							} else {
9514 								endContainer = prev;
9515 								endOffset = idx;
9516 							}
9517 						}
9518 					}
9519 				}
9520 			};
9521 
9522 			function addBogus(node) {
9523 				// Adds a bogus BR element for empty block elements
9524 				if (dom.isBlock(node) && !node.innerHTML && !isIE)
9525 					node.innerHTML = '<br data-mce-bogus="1" />';
9526 
9527 				return node;
9528 			};
9529 
9530 			if (bookmark) {
9531 				if (bookmark.start) {
9532 					rng = dom.createRng();
9533 					root = dom.getRoot();
9534 
9535 					if (t.tridentSel)
9536 						return t.tridentSel.moveToBookmark(bookmark);
9537 
9538 					if (setEndPoint(true) && setEndPoint()) {
9539 						t.setRng(rng);
9540 					}
9541 				} else if (bookmark.id) {
9542 					// Restore start/end points
9543 					restoreEndPoint('start');
9544 					restoreEndPoint('end');
9545 
9546 					if (startContainer) {
9547 						rng = dom.createRng();
9548 						rng.setStart(addBogus(startContainer), startOffset);
9549 						rng.setEnd(addBogus(endContainer), endOffset);
9550 						t.setRng(rng);
9551 					}
9552 				} else if (bookmark.name) {
9553 					t.select(dom.select(bookmark.name)[bookmark.index]);
9554 				} else if (bookmark.rng)
9555 					t.setRng(bookmark.rng);
9556 			}
9557 		},
9558 
9559 		select : function(node, content) {
9560 			var t = this, dom = t.dom, rng = dom.createRng(), idx;
9561 
9562 			function setPoint(node, start) {
9563 				var walker = new TreeWalker(node, node);
9564 
9565 				do {
9566 					// Text node
9567 					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
9568 						if (start)
9569 							rng.setStart(node, 0);
9570 						else
9571 							rng.setEnd(node, node.nodeValue.length);
9572 
9573 						return;
9574 					}
9575 
9576 					// BR element
9577 					if (node.nodeName == 'BR') {
9578 						if (start)
9579 							rng.setStartBefore(node);
9580 						else
9581 							rng.setEndBefore(node);
9582 
9583 						return;
9584 					}
9585 				} while (node = (start ? walker.next() : walker.prev()));
9586 			};
9587 
9588 			if (node) {
9589 				idx = dom.nodeIndex(node);
9590 				rng.setStart(node.parentNode, idx);
9591 				rng.setEnd(node.parentNode, idx + 1);
9592 
9593 				// Find first/last text node or BR element
9594 				if (content) {
9595 					setPoint(node, 1);
9596 					setPoint(node);
9597 				}
9598 
9599 				t.setRng(rng);
9600 			}
9601 
9602 			return node;
9603 		},
9604 
9605 		isCollapsed : function() {
9606 			var t = this, r = t.getRng(), s = t.getSel();
9607 
9608 			if (!r || r.item)
9609 				return false;
9610 
9611 			if (r.compareEndPoints)
9612 				return r.compareEndPoints('StartToEnd', r) === 0;
9613 
9614 			return !s || r.collapsed;
9615 		},
9616 
9617 		collapse : function(to_start) {
9618 			var self = this, rng = self.getRng(), node;
9619 
9620 			// Control range on IE
9621 			if (rng.item) {
9622 				node = rng.item(0);
9623 				rng = self.win.document.body.createTextRange();
9624 				rng.moveToElementText(node);
9625 			}
9626 
9627 			rng.collapse(!!to_start);
9628 			self.setRng(rng);
9629 		},
9630 
9631 		getSel : function() {
9632 			var t = this, w = this.win;
9633 
9634 			return w.getSelection ? w.getSelection() : w.document.selection;
9635 		},
9636 
9637 		getRng : function(w3c) {
9638 			var self = this, selection, rng, elm, doc = self.win.document;
9639 
9640 			// Found tridentSel object then we need to use that one
9641 			if (w3c && self.tridentSel) {
9642 				return self.tridentSel.getRangeAt(0);
9643 			}
9644 
9645 			try {
9646 				if (selection = self.getSel()) {
9647 					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9648 				}
9649 			} catch (ex) {
9650 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9651 			}
9652 
9653 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
9654 			if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
9655 				elm = doc.selection.createRange().item(0);
9656 				rng = doc.createRange();
9657 				rng.setStartBefore(elm);
9658 				rng.setEndAfter(elm);
9659 			}
9660 
9661 			// No range found then create an empty one
9662 			// This can occur when the editor is placed in a hidden container element on Gecko
9663 			// Or on IE when there was an exception
9664 			if (!rng) {
9665 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
9666 			}
9667 
9668 			// If range is at start of document then move it to start of body
9669 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
9670 				elm = self.dom.getRoot();
9671 				rng.setStart(elm, 0);
9672 				rng.setEnd(elm, 0);
9673 			}
9674 
9675 			if (self.selectedRange && self.explicitRange) {
9676 				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
9677 					// Safari, Opera and Chrome only ever select text which causes the range to change.
9678 					// This lets us use the originally set range if the selection hasn't been changed by the user.
9679 					rng = self.explicitRange;
9680 				} else {
9681 					self.selectedRange = null;
9682 					self.explicitRange = null;
9683 				}
9684 			}
9685 
9686 			return rng;
9687 		},
9688 
9689 		setRng : function(r, forward) {
9690 			var s, t = this;
9691 
9692 			if (!t.tridentSel) {
9693 				s = t.getSel();
9694 
9695 				if (s) {
9696 					t.explicitRange = r;
9697 
9698 					try {
9699 						s.removeAllRanges();
9700 					} catch (ex) {
9701 						// IE9 might throw errors here don't know why
9702 					}
9703 
9704 					s.addRange(r);
9705 
9706 					// Forward is set to false and we have an extend function
9707 					if (forward === false && s.extend) {
9708 						s.collapse(r.endContainer, r.endOffset);
9709 						s.extend(r.startContainer, r.startOffset);
9710 					}
9711 
9712 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
9713 					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
9714 				}
9715 			} else {
9716 				// Is W3C Range
9717 				if (r.cloneRange) {
9718 					try {
9719 						t.tridentSel.addRange(r);
9720 						return;
9721 					} catch (ex) {
9722 						//IE9 throws an error here if called before selection is placed in the editor
9723 					}
9724 				}
9725 
9726 				// Is IE specific range
9727 				try {
9728 					r.select();
9729 				} catch (ex) {
9730 					// Needed for some odd IE bug #1843306
9731 				}
9732 			}
9733 		},
9734 
9735 		setNode : function(n) {
9736 			var t = this;
9737 
9738 			t.setContent(t.dom.getOuterHTML(n));
9739 
9740 			return n;
9741 		},
9742 
9743 		getNode : function() {
9744 			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
9745 
9746 			function skipEmptyTextNodes(n, forwards) {
9747 				var orig = n;
9748 				while (n && n.nodeType === 3 && n.length === 0) {
9749 					n = forwards ? n.nextSibling : n.previousSibling;
9750 				}
9751 				return n || orig;
9752 			};
9753 
9754 			// Range maybe lost after the editor is made visible again
9755 			if (!rng)
9756 				return t.dom.getRoot();
9757 
9758 			if (rng.setStart) {
9759 				elm = rng.commonAncestorContainer;
9760 
9761 				// Handle selection a image or other control like element such as anchors
9762 				if (!rng.collapsed) {
9763 					if (rng.startContainer == rng.endContainer) {
9764 						if (rng.endOffset - rng.startOffset < 2) {
9765 							if (rng.startContainer.hasChildNodes())
9766 								elm = rng.startContainer.childNodes[rng.startOffset];
9767 						}
9768 					}
9769 
9770 					// If the anchor node is a element instead of a text node then return this element
9771 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
9772 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
9773 
9774 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
9775 					// This happens when you double click an underlined word in FireFox.
9776 					if (start.nodeType === 3 && end.nodeType === 3) {
9777 						if (start.length === rng.startOffset) {
9778 							start = skipEmptyTextNodes(start.nextSibling, true);
9779 						} else {
9780 							start = start.parentNode;
9781 						}
9782 						if (rng.endOffset === 0) {
9783 							end = skipEmptyTextNodes(end.previousSibling, false);
9784 						} else {
9785 							end = end.parentNode;
9786 						}
9787 
9788 						if (start && start === end)
9789 							return start;
9790 					}
9791 				}
9792 
9793 				if (elm && elm.nodeType == 3)
9794 					return elm.parentNode;
9795 
9796 				return elm;
9797 			}
9798 
9799 			return rng.item ? rng.item(0) : rng.parentElement();
9800 		},
9801 
9802 		getSelectedBlocks : function(st, en) {
9803 			var t = this, dom = t.dom, sb, eb, n, bl = [];
9804 
9805 			sb = dom.getParent(st || t.getStart(), dom.isBlock);
9806 			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
9807 
9808 			if (sb)
9809 				bl.push(sb);
9810 
9811 			if (sb && eb && sb != eb) {
9812 				n = sb;
9813 
9814 				var walker = new TreeWalker(sb, dom.getRoot());
9815 				while ((n = walker.next()) && n != eb) {
9816 					if (dom.isBlock(n))
9817 						bl.push(n);
9818 				}
9819 			}
9820 
9821 			if (eb && sb != eb)
9822 				bl.push(eb);
9823 
9824 			return bl;
9825 		},
9826 
9827 		isForward: function(){
9828 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
9829 
9830 			// No support for selection direction then always return true
9831 			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
9832 				return true;
9833 			}
9834 
9835 			anchorRange = dom.createRng();
9836 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
9837 			anchorRange.collapse(true);
9838 
9839 			focusRange = dom.createRng();
9840 			focusRange.setStart(sel.focusNode, sel.focusOffset);
9841 			focusRange.collapse(true);
9842 
9843 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
9844 		},
9845 
9846 		normalize : function() {
9847 			var self = this, rng, normalized, collapsed, node, sibling;
9848 
9849 			function normalizeEndPoint(start) {
9850 				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
9851 
9852 				function hasBrBeforeAfter(node, left) {
9853 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
9854 
9855 					while (node = walker[left ? 'prev' : 'next']()) {
9856 						if (node.nodeName === "BR") {
9857 							return true;
9858 						}
9859 					}
9860 				};
9861 
9862 				// Walks the dom left/right to find a suitable text node to move the endpoint into
9863 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
9864 				function findTextNodeRelative(left, startNode) {
9865 					var walker, lastInlineElement;
9866 
9867 					startNode = startNode || container;
9868 					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
9869 
9870 					// Walk left until we hit a text node we can move to or a block/br/img
9871 					while (node = walker[left ? 'prev' : 'next']()) {
9872 						// Found text node that has a length
9873 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
9874 							container = node;
9875 							offset = left ? node.nodeValue.length : 0;
9876 							normalized = true;
9877 							return;
9878 						}
9879 
9880 						// Break if we find a block or a BR/IMG/INPUT etc
9881 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9882 							return;
9883 						}
9884 
9885 						lastInlineElement = node;
9886 					}
9887 
9888 					// Only fetch the last inline element when in caret mode for now
9889 					if (collapsed && lastInlineElement) {
9890 						container = lastInlineElement;
9891 						normalized = true;
9892 						offset = 0;
9893 					}
9894 				};
9895 
9896 				container = rng[(start ? 'start' : 'end') + 'Container'];
9897 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
9898 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
9899 
9900 				// If the container is a document move it to the body element
9901 				if (container.nodeType === 9) {
9902 					container = dom.getRoot();
9903 					offset = 0;
9904 				}
9905 
9906 				// If the container is body try move it into the closest text node or position
9907 				if (container === body) {
9908 					// If start is before/after a image, table etc
9909 					if (start) {
9910 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
9911 						if (node) {
9912 							nodeName = node.nodeName.toLowerCase();
9913 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
9914 								return;
9915 							}
9916 						}
9917 					}
9918 
9919 					// Resolve the index
9920 					if (container.hasChildNodes()) {
9921 						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
9922 						offset = 0;
9923 
9924 						// Don't walk into elements that doesn't have any child nodes like a IMG
9925 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
9926 							// Walk the DOM to find a text node to place the caret at or a BR
9927 							node = container;
9928 							walker = new TreeWalker(container, body);
9929 
9930 							do {
9931 								// Found a text node use that position
9932 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
9933 									offset = start ? 0 : node.nodeValue.length;
9934 									container = node;
9935 									normalized = true;
9936 									break;
9937 								}
9938 
9939 								// Found a BR/IMG element that we can place the caret before
9940 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9941 									offset = dom.nodeIndex(node);
9942 									container = node.parentNode;
9943 
9944 									// Put caret after image when moving the end point
9945 									if (node.nodeName ==  "IMG" && !start) {
9946 										offset++;
9947 									}
9948 
9949 									normalized = true;
9950 									break;
9951 								}
9952 							} while (node = (start ? walker.next() : walker.prev()));
9953 						}
9954 					}
9955 				}
9956 
9957 				// Lean the caret to the left if possible
9958 				if (collapsed) {
9959 					// So this: <b>x</b><i>|x</i>
9960 					// Becomes: <b>x|</b><i>x</i>
9961 					// Seems that only gecko has issues with this
9962 					if (container.nodeType === 3 && offset === 0) {
9963 						findTextNodeRelative(true);
9964 					}
9965 
9966 					// Lean left into empty inline elements when the caret is before a BR
9967 					// So this: <i><b></b><i>|<br></i>
9968 					// Becomes: <i><b>|</b><i><br></i>
9969 					// Seems that only gecko has issues with this
9970 					if (container.nodeType === 1) {
9971 						node = container.childNodes[offset];
9972 						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
9973 							findTextNodeRelative(true, container.childNodes[offset]);
9974 						}
9975 					}
9976 				}
9977 
9978 				// Lean the start of the selection right if possible
9979 				// So this: x[<b>x]</b>
9980 				// Becomes: x<b>[x]</b>
9981 				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
9982 					findTextNodeRelative(false);
9983 				}
9984 
9985 				// Set endpoint if it was normalized
9986 				if (normalized)
9987 					rng['set' + (start ? 'Start' : 'End')](container, offset);
9988 			};
9989 
9990 			// Normalize only on non IE browsers for now
9991 			if (tinymce.isIE)
9992 				return;
9993 			
9994 			rng = self.getRng();
9995 			collapsed = rng.collapsed;
9996 
9997 			// Normalize the end points
9998 			normalizeEndPoint(true);
9999 
10000 			if (!collapsed)
10001 				normalizeEndPoint();
10002 
10003 			// Set the selection if it was normalized
10004 			if (normalized) {
10005 				// If it was collapsed then make sure it still is
10006 				if (collapsed) {
10007 					rng.collapse(true);
10008 				}
10009 
10010 				//console.log(self.dom.dumpRng(rng));
10011 				self.setRng(rng, self.isForward());
10012 			}
10013 		},
10014 
10015 		selectorChanged: function(selector, callback) {
10016 			var self = this, currentSelectors;
10017 
10018 			if (!self.selectorChangedData) {
10019 				self.selectorChangedData = {};
10020 				currentSelectors = {};
10021 
10022 				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
10023 					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10024 
10025 					// Check for new matching selectors
10026 					each(self.selectorChangedData, function(callbacks, selector) {
10027 						each(parents, function(node) {
10028 							if (dom.is(node, selector)) {
10029 								if (!currentSelectors[selector]) {
10030 									// Execute callbacks
10031 									each(callbacks, function(callback) {
10032 										callback(true, {node: node, selector: selector, parents: parents});
10033 									});
10034 
10035 									currentSelectors[selector] = callbacks;
10036 								}
10037 
10038 								matchedSelectors[selector] = callbacks;
10039 								return false;
10040 							}
10041 						});
10042 					});
10043 
10044 					// Check if current selectors still match
10045 					each(currentSelectors, function(callbacks, selector) {
10046 						if (!matchedSelectors[selector]) {
10047 							delete currentSelectors[selector];
10048 
10049 							each(callbacks, function(callback) {
10050 								callback(false, {node: node, selector: selector, parents: parents});
10051 							});
10052 						}
10053 					});
10054 				});
10055 			}
10056 
10057 			// Add selector listeners
10058 			if (!self.selectorChangedData[selector]) {
10059 				self.selectorChangedData[selector] = [];
10060 			}
10061 
10062 			self.selectorChangedData[selector].push(callback);
10063 
10064 			return self;
10065 		},
10066 
10067 		destroy : function(manual) {
10068 			var self = this;
10069 
10070 			self.win = null;
10071 
10072 			// Manual destroy then remove unload handler
10073 			if (!manual)
10074 				tinymce.removeUnload(self.destroy);
10075 		},
10076 
10077 		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
10078 		_fixIESelection : function() {
10079 			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
10080 
10081 			// Return range from point or null if it failed
10082 			function rngFromPoint(x, y) {
10083 				var rng = body.createTextRange();
10084 
10085 				try {
10086 					rng.moveToPoint(x, y);
10087 				} catch (ex) {
10088 					// IE sometimes throws and exception, so lets just ignore it
10089 					rng = null;
10090 				}
10091 
10092 				return rng;
10093 			};
10094 
10095 			// Fires while the selection is changing
10096 			function selectionChange(e) {
10097 				var pointRng;
10098 
10099 				// Check if the button is down or not
10100 				if (e.button) {
10101 					// Create range from mouse position
10102 					pointRng = rngFromPoint(e.x, e.y);
10103 
10104 					if (pointRng) {
10105 						// Check if pointRange is before/after selection then change the endPoint
10106 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
10107 							pointRng.setEndPoint('StartToStart', startRng);
10108 						else
10109 							pointRng.setEndPoint('EndToEnd', startRng);
10110 
10111 						pointRng.select();
10112 					}
10113 				} else
10114 					endSelection();
10115 			}
10116 
10117 			// Removes listeners
10118 			function endSelection() {
10119 				var rng = doc.selection.createRange();
10120 
10121 				// If the range is collapsed then use the last start range
10122 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
10123 					startRng.select();
10124 
10125 				dom.unbind(doc, 'mouseup', endSelection);
10126 				dom.unbind(doc, 'mousemove', selectionChange);
10127 				startRng = started = 0;
10128 			};
10129 
10130 			// Make HTML element unselectable since we are going to handle selection by hand
10131 			doc.documentElement.unselectable = true;
10132 			
10133 			// Detect when user selects outside BODY
10134 			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
10135 				if (e.target.nodeName === 'HTML') {
10136 					if (started)
10137 						endSelection();
10138 
10139 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
10140 					htmlElm = doc.documentElement;
10141 					if (htmlElm.scrollHeight > htmlElm.clientHeight)
10142 						return;
10143 
10144 					started = 1;
10145 					// Setup start position
10146 					startRng = rngFromPoint(e.x, e.y);
10147 					if (startRng) {
10148 						// Listen for selection change events
10149 						dom.bind(doc, 'mouseup', endSelection);
10150 						dom.bind(doc, 'mousemove', selectionChange);
10151 
10152 						dom.win.focus();
10153 						startRng.select();
10154 					}
10155 				}
10156 			});
10157 		}
10158 	});
10159 })(tinymce);
10160 
10161 (function(tinymce) {
10162 	tinymce.dom.Serializer = function(settings, dom, schema) {
10163 		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
10164 
10165 		// Support the old apply_source_formatting option
10166 		if (!settings.apply_source_formatting)
10167 			settings.indent = false;
10168 
10169 		// Default DOM and Schema if they are undefined
10170 		dom = dom || tinymce.DOM;
10171 		schema = schema || new tinymce.html.Schema(settings);
10172 		settings.entity_encoding = settings.entity_encoding || 'named';
10173 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10174 
10175 		onPreProcess = new tinymce.util.Dispatcher(self);
10176 
10177 		onPostProcess = new tinymce.util.Dispatcher(self);
10178 
10179 		htmlParser = new tinymce.html.DomParser(settings, schema);
10180 
10181 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10182 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10183 			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10184 
10185 			while (i--) {
10186 				node = nodes[i];
10187 
10188 				value = node.attributes.map[internalName];
10189 				if (value !== undef) {
10190 					// Set external name to internal value and remove internal
10191 					node.attr(name, value.length > 0 ? value : null);
10192 					node.attr(internalName, null);
10193 				} else {
10194 					// No internal attribute found then convert the value we have in the DOM
10195 					value = node.attributes.map[name];
10196 
10197 					if (name === "style")
10198 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
10199 					else if (urlConverter)
10200 						value = urlConverter.call(urlConverterScope, value, name, node.name);
10201 
10202 					node.attr(name, value.length > 0 ? value : null);
10203 				}
10204 			}
10205 		});
10206 
10207 		// Remove internal classes mceItem<..> or mceSelected
10208 		htmlParser.addAttributeFilter('class', function(nodes, name) {
10209 			var i = nodes.length, node, value;
10210 
10211 			while (i--) {
10212 				node = nodes[i];
10213 				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
10214 				node.attr('class', value.length > 0 ? value : null);
10215 			}
10216 		});
10217 
10218 		// Remove bookmark elements
10219 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10220 			var i = nodes.length, node;
10221 
10222 			while (i--) {
10223 				node = nodes[i];
10224 
10225 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
10226 					node.remove();
10227 			}
10228 		});
10229 
10230 		// Remove expando attributes
10231 		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
10232 			var i = nodes.length;
10233 
10234 			while (i--) {
10235 				nodes[i].attr(name, null);
10236 			}
10237 		});
10238 
10239 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
10240 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
10241 			var i = nodes.length, node, value;
10242 
10243 			function trim(value) {
10244 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10245 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
10246 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10247 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10248 			};
10249 
10250 			while (i--) {
10251 				node = nodes[i];
10252 				value = node.firstChild ? node.firstChild.value : '';
10253 
10254 				if (name === "script") {
10255 					// Remove mce- prefix from script elements
10256 					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
10257 
10258 					if (value.length > 0)
10259 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10260 				} else {
10261 					if (value.length > 0)
10262 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10263 				}
10264 			}
10265 		});
10266 
10267 		// Convert comments to cdata and handle protected comments
10268 		htmlParser.addNodeFilter('#comment', function(nodes, name) {
10269 			var i = nodes.length, node;
10270 
10271 			while (i--) {
10272 				node = nodes[i];
10273 
10274 				if (node.value.indexOf('[CDATA[') === 0) {
10275 					node.name = '#cdata';
10276 					node.type = 4;
10277 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10278 				} else if (node.value.indexOf('mce:protected ') === 0) {
10279 					node.name = "#text";
10280 					node.type = 3;
10281 					node.raw = true;
10282 					node.value = unescape(node.value).substr(14);
10283 				}
10284 			}
10285 		});
10286 
10287 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10288 			var i = nodes.length, node;
10289 
10290 			while (i--) {
10291 				node = nodes[i];
10292 				if (node.type === 7)
10293 					node.remove();
10294 				else if (node.type === 1) {
10295 					if (name === "input" && !("type" in node.attributes.map))
10296 						node.attr('type', 'text');
10297 				}
10298 			}
10299 		});
10300 
10301 		// Fix list elements, TODO: Replace this later
10302 		if (settings.fix_list_elements) {
10303 			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
10304 				var i = nodes.length, node, parentNode;
10305 
10306 				while (i--) {
10307 					node = nodes[i];
10308 					parentNode = node.parent;
10309 
10310 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10311 						if (node.prev && node.prev.name === 'li') {
10312 							node.prev.append(node);
10313 						}
10314 					}
10315 				}
10316 			});
10317 		}
10318 
10319 		// Remove internal data attributes
10320 		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
10321 			var i = nodes.length;
10322 
10323 			while (i--) {
10324 				nodes[i].attr(name, null);
10325 			}
10326 		});
10327 
10328 		// Return public methods
10329 		return {
10330 			schema : schema,
10331 
10332 			addNodeFilter : htmlParser.addNodeFilter,
10333 
10334 			addAttributeFilter : htmlParser.addAttributeFilter,
10335 
10336 			onPreProcess : onPreProcess,
10337 
10338 			onPostProcess : onPostProcess,
10339 
10340 			serialize : function(node, args) {
10341 				var impl, doc, oldDoc, htmlSerializer, content;
10342 
10343 				// Explorer won't clone contents of script and style and the
10344 				// selected index of select elements are cleared on a clone operation.
10345 				if (isIE && dom.select('script,style,select,map').length > 0) {
10346 					content = node.innerHTML;
10347 					node = node.cloneNode(false);
10348 					dom.setHTML(node, content);
10349 				} else
10350 					node = node.cloneNode(true);
10351 
10352 				// Nodes needs to be attached to something in WebKit/Opera
10353 				// Older builds of Opera crashes if you attach the node to an document created dynamically
10354 				// and since we can't feature detect a crash we need to sniff the acutal build number
10355 				// This fix will make DOM ranges and make Sizzle happy!
10356 				impl = node.ownerDocument.implementation;
10357 				if (impl.createHTMLDocument) {
10358 					// Create an empty HTML document
10359 					doc = impl.createHTMLDocument("");
10360 
10361 					// Add the element or it's children if it's a body element to the new document
10362 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
10363 						doc.body.appendChild(doc.importNode(node, true));
10364 					});
10365 
10366 					// Grab first child or body element for serialization
10367 					if (node.nodeName != 'BODY')
10368 						node = doc.body.firstChild;
10369 					else
10370 						node = doc.body;
10371 
10372 					// set the new document in DOMUtils so createElement etc works
10373 					oldDoc = dom.doc;
10374 					dom.doc = doc;
10375 				}
10376 
10377 				args = args || {};
10378 				args.format = args.format || 'html';
10379 
10380 				// Pre process
10381 				if (!args.no_events) {
10382 					args.node = node;
10383 					onPreProcess.dispatch(self, args);
10384 				}
10385 
10386 				// Setup serializer
10387 				htmlSerializer = new tinymce.html.Serializer(settings, schema);
10388 
10389 				// Parse and serialize HTML
10390 				args.content = htmlSerializer.serialize(
10391 					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
10392 				);
10393 
10394 				// Replace all BOM characters for now until we can find a better solution
10395 				if (!args.cleanup)
10396 					args.content = args.content.replace(/\uFEFF|\u200B/g, '');
10397 
10398 				// Post process
10399 				if (!args.no_events)
10400 					onPostProcess.dispatch(self, args);
10401 
10402 				// Restore the old document if it was changed
10403 				if (oldDoc)
10404 					dom.doc = oldDoc;
10405 
10406 				args.node = null;
10407 
10408 				return args.content;
10409 			},
10410 
10411 			addRules : function(rules) {
10412 				schema.addValidElements(rules);
10413 			},
10414 
10415 			setRules : function(rules) {
10416 				schema.setValidElements(rules);
10417 			}
10418 		};
10419 	};
10420 })(tinymce);
10421 (function(tinymce) {
10422 	tinymce.dom.ScriptLoader = function(settings) {
10423 		var QUEUED = 0,
10424 			LOADING = 1,
10425 			LOADED = 2,
10426 			states = {},
10427 			queue = [],
10428 			scriptLoadedCallbacks = {},
10429 			queueLoadedCallbacks = [],
10430 			loading = 0,
10431 			undef;
10432 
10433 		function loadScript(url, callback) {
10434 			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
10435 
10436 			// Execute callback when script is loaded
10437 			function done() {
10438 				dom.remove(id);
10439 
10440 				if (elm)
10441 					elm.onreadystatechange = elm.onload = elm = null;
10442 
10443 				callback();
10444 			};
10445 			
10446 			function error() {
10447 				// Report the error so it's easier for people to spot loading errors
10448 				if (typeof(console) !== "undefined" && console.log)
10449 					console.log("Failed to load: " + url);
10450 
10451 				// We can't mark it as done if there is a load error since
10452 				// A) We don't want to produce 404 errors on the server and
10453 				// B) the onerror event won't fire on all browsers.
10454 				// done();
10455 			};
10456 
10457 			id = dom.uniqueId();
10458 
10459 			if (tinymce.isIE6) {
10460 				uri = new tinymce.util.URI(url);
10461 				loc = location;
10462 
10463 				// If script is from same domain and we
10464 				// use IE 6 then use XHR since it's more reliable
10465 				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
10466 					tinymce.util.XHR.send({
10467 						url : tinymce._addVer(uri.getURI()),
10468 						success : function(content) {
10469 							// Create new temp script element
10470 							var script = dom.create('script', {
10471 								type : 'text/javascript'
10472 							});
10473 
10474 							// Evaluate script in global scope
10475 							script.text = content;
10476 							document.getElementsByTagName('head')[0].appendChild(script);
10477 							dom.remove(script);
10478 
10479 							done();
10480 						},
10481 						
10482 						error : error
10483 					});
10484 
10485 					return;
10486 				}
10487 			}
10488 
10489 			// Create new script element
10490 			elm = document.createElement('script');
10491 			elm.id = id;
10492 			elm.type = 'text/javascript';
10493 			elm.src = tinymce._addVer(url);
10494 
10495 			// Add onload listener for non IE browsers since IE9
10496 			// fires onload event before the script is parsed and executed
10497 			if (!tinymce.isIE)
10498 				elm.onload = done;
10499 
10500 			// Add onerror event will get fired on some browsers but not all of them
10501 			elm.onerror = error;
10502 
10503 			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
10504 			if (!tinymce.isOpera) {
10505 				elm.onreadystatechange = function() {
10506 					var state = elm.readyState;
10507 
10508 					// Loaded state is passed on IE 6 however there
10509 					// are known issues with this method but we can't use
10510 					// XHR in a cross domain loading
10511 					if (state == 'complete' || state == 'loaded')
10512 						done();
10513 				};
10514 			}
10515 
10516 			// Most browsers support this feature so we report errors
10517 			// for those at least to help users track their missing plugins etc
10518 			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
10519 			/*elm.onerror = function() {
10520 				alert('Failed to load: ' + url);
10521 			};*/
10522 
10523 			// Add script to document
10524 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
10525 		};
10526 
10527 		this.isDone = function(url) {
10528 			return states[url] == LOADED;
10529 		};
10530 
10531 		this.markDone = function(url) {
10532 			states[url] = LOADED;
10533 		};
10534 
10535 		this.add = this.load = function(url, callback, scope) {
10536 			var item, state = states[url];
10537 
10538 			// Add url to load queue
10539 			if (state == undef) {
10540 				queue.push(url);
10541 				states[url] = QUEUED;
10542 			}
10543 
10544 			if (callback) {
10545 				// Store away callback for later execution
10546 				if (!scriptLoadedCallbacks[url])
10547 					scriptLoadedCallbacks[url] = [];
10548 
10549 				scriptLoadedCallbacks[url].push({
10550 					func : callback,
10551 					scope : scope || this
10552 				});
10553 			}
10554 		};
10555 
10556 		this.loadQueue = function(callback, scope) {
10557 			this.loadScripts(queue, callback, scope);
10558 		};
10559 
10560 		this.loadScripts = function(scripts, callback, scope) {
10561 			var loadScripts;
10562 
10563 			function execScriptLoadedCallbacks(url) {
10564 				// Execute URL callback functions
10565 				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
10566 					callback.func.call(callback.scope);
10567 				});
10568 
10569 				scriptLoadedCallbacks[url] = undef;
10570 			};
10571 
10572 			queueLoadedCallbacks.push({
10573 				func : callback,
10574 				scope : scope || this
10575 			});
10576 
10577 			loadScripts = function() {
10578 				var loadingScripts = tinymce.grep(scripts);
10579 
10580 				// Current scripts has been handled
10581 				scripts.length = 0;
10582 
10583 				// Load scripts that needs to be loaded
10584 				tinymce.each(loadingScripts, function(url) {
10585 					// Script is already loaded then execute script callbacks directly
10586 					if (states[url] == LOADED) {
10587 						execScriptLoadedCallbacks(url);
10588 						return;
10589 					}
10590 
10591 					// Is script not loading then start loading it
10592 					if (states[url] != LOADING) {
10593 						states[url] = LOADING;
10594 						loading++;
10595 
10596 						loadScript(url, function() {
10597 							states[url] = LOADED;
10598 							loading--;
10599 
10600 							execScriptLoadedCallbacks(url);
10601 
10602 							// Load more scripts if they where added by the recently loaded script
10603 							loadScripts();
10604 						});
10605 					}
10606 				});
10607 
10608 				// No scripts are currently loading then execute all pending queue loaded callbacks
10609 				if (!loading) {
10610 					tinymce.each(queueLoadedCallbacks, function(callback) {
10611 						callback.func.call(callback.scope);
10612 					});
10613 
10614 					queueLoadedCallbacks.length = 0;
10615 				}
10616 			};
10617 
10618 			loadScripts();
10619 		};
10620 	};
10621 
10622 	// Global script loader
10623 	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
10624 })(tinymce);
10625 
10626 (function(tinymce) {
10627 	tinymce.dom.RangeUtils = function(dom) {
10628 		var INVISIBLE_CHAR = '\uFEFF';
10629 
10630 		this.walk = function(rng, callback) {
10631 			var startContainer = rng.startContainer,
10632 				startOffset = rng.startOffset,
10633 				endContainer = rng.endContainer,
10634 				endOffset = rng.endOffset,
10635 				ancestor, startPoint,
10636 				endPoint, node, parent, siblings, nodes;
10637 
10638 			// Handle table cell selection the table plugin enables
10639 			// you to fake select table cells and perform formatting actions on them
10640 			nodes = dom.select('td.mceSelected,th.mceSelected');
10641 			if (nodes.length > 0) {
10642 				tinymce.each(nodes, function(node) {
10643 					callback([node]);
10644 				});
10645 
10646 				return;
10647 			}
10648 
10649 			function exclude(nodes) {
10650 				var node;
10651 
10652 				// First node is excluded
10653 				node = nodes[0];
10654 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
10655 					nodes.splice(0, 1);
10656 				}
10657 
10658 				// Last node is excluded
10659 				node = nodes[nodes.length - 1];
10660 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
10661 					nodes.splice(nodes.length - 1, 1);
10662 				}
10663 
10664 				return nodes;
10665 			};
10666 
10667 			function collectSiblings(node, name, end_node) {
10668 				var siblings = [];
10669 
10670 				for (; node && node != end_node; node = node[name])
10671 					siblings.push(node);
10672 
10673 				return siblings;
10674 			};
10675 
10676 			function findEndPoint(node, root) {
10677 				do {
10678 					if (node.parentNode == root)
10679 						return node;
10680 
10681 					node = node.parentNode;
10682 				} while(node);
10683 			};
10684 
10685 			function walkBoundary(start_node, end_node, next) {
10686 				var siblingName = next ? 'nextSibling' : 'previousSibling';
10687 
10688 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
10689 					parent = node.parentNode;
10690 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
10691 
10692 					if (siblings.length) {
10693 						if (!next)
10694 							siblings.reverse();
10695 
10696 						callback(exclude(siblings));
10697 					}
10698 				}
10699 			};
10700 
10701 			// If index based start position then resolve it
10702 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
10703 				startContainer = startContainer.childNodes[startOffset];
10704 
10705 			// If index based end position then resolve it
10706 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
10707 				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
10708 
10709 			// Same container
10710 			if (startContainer == endContainer)
10711 				return callback(exclude([startContainer]));
10712 
10713 			// Find common ancestor and end points
10714 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
10715 				
10716 			// Process left side
10717 			for (node = startContainer; node; node = node.parentNode) {
10718 				if (node === endContainer)
10719 					return walkBoundary(startContainer, ancestor, true);
10720 
10721 				if (node === ancestor)
10722 					break;
10723 			}
10724 
10725 			// Process right side
10726 			for (node = endContainer; node; node = node.parentNode) {
10727 				if (node === startContainer)
10728 					return walkBoundary(endContainer, ancestor);
10729 
10730 				if (node === ancestor)
10731 					break;
10732 			}
10733 
10734 			// Find start/end point
10735 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
10736 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
10737 
10738 			// Walk left leaf
10739 			walkBoundary(startContainer, startPoint, true);
10740 
10741 			// Walk the middle from start to end point
10742 			siblings = collectSiblings(
10743 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
10744 				'nextSibling',
10745 				endPoint == endContainer ? endPoint.nextSibling : endPoint
10746 			);
10747 
10748 			if (siblings.length)
10749 				callback(exclude(siblings));
10750 
10751 			// Walk right leaf
10752 			walkBoundary(endContainer, endPoint);
10753 		};
10754 
10755 		this.split = function(rng) {
10756 			var startContainer = rng.startContainer,
10757 				startOffset = rng.startOffset,
10758 				endContainer = rng.endContainer,
10759 				endOffset = rng.endOffset;
10760 
10761 			function splitText(node, offset) {
10762 				return node.splitText(offset);
10763 			};
10764 
10765 			// Handle single text node
10766 			if (startContainer == endContainer && startContainer.nodeType == 3) {
10767 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10768 					endContainer = splitText(startContainer, startOffset);
10769 					startContainer = endContainer.previousSibling;
10770 
10771 					if (endOffset > startOffset) {
10772 						endOffset = endOffset - startOffset;
10773 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
10774 						endOffset = endContainer.nodeValue.length;
10775 						startOffset = 0;
10776 					} else {
10777 						endOffset = 0;
10778 					}
10779 				}
10780 			} else {
10781 				// Split startContainer text node if needed
10782 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10783 					startContainer = splitText(startContainer, startOffset);
10784 					startOffset = 0;
10785 				}
10786 
10787 				// Split endContainer text node if needed
10788 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
10789 					endContainer = splitText(endContainer, endOffset).previousSibling;
10790 					endOffset = endContainer.nodeValue.length;
10791 				}
10792 			}
10793 
10794 			return {
10795 				startContainer : startContainer,
10796 				startOffset : startOffset,
10797 				endContainer : endContainer,
10798 				endOffset : endOffset
10799 			};
10800 		};
10801 
10802 	};
10803 
10804 	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
10805 		if (rng1 && rng2) {
10806 			// Compare native IE ranges
10807 			if (rng1.item || rng1.duplicate) {
10808 				// Both are control ranges and the selected element matches
10809 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
10810 					return true;
10811 
10812 				// Both are text ranges and the range matches
10813 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
10814 					return true;
10815 			} else {
10816 				// Compare w3c ranges
10817 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
10818 			}
10819 		}
10820 
10821 		return false;
10822 	};
10823 })(tinymce);
10824 
10825 (function(tinymce) {
10826 	var Event = tinymce.dom.Event, each = tinymce.each;
10827 
10828 	tinymce.create('tinymce.ui.KeyboardNavigation', {
10829 		KeyboardNavigation: function(settings, dom) {
10830 			var t = this, root = settings.root, items = settings.items,
10831 					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
10832 					excludeFromTabOrder = settings.excludeFromTabOrder,
10833 					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
10834 
10835 			dom = dom || tinymce.DOM;
10836 
10837 			itemFocussed = function(evt) {
10838 				focussedId = evt.target.id;
10839 			};
10840 			
10841 			itemBlurred = function(evt) {
10842 				dom.setAttrib(evt.target.id, 'tabindex', '-1');
10843 			};
10844 			
10845 			rootFocussed = function(evt) {
10846 				var item = dom.get(focussedId);
10847 				dom.setAttrib(item, 'tabindex', '0');
10848 				item.focus();
10849 			};
10850 			
10851 			t.focus = function() {
10852 				dom.get(focussedId).focus();
10853 			};
10854 
10855 			t.destroy = function() {
10856 				each(items, function(item) {
10857 					var elm = dom.get(item.id);
10858 
10859 					dom.unbind(elm, 'focus', itemFocussed);
10860 					dom.unbind(elm, 'blur', itemBlurred);
10861 				});
10862 
10863 				var rootElm = dom.get(root);
10864 				dom.unbind(rootElm, 'focus', rootFocussed);
10865 				dom.unbind(rootElm, 'keydown', rootKeydown);
10866 
10867 				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
10868 				t.destroy = function() {};
10869 			};
10870 			
10871 			t.moveFocus = function(dir, evt) {
10872 				var idx = -1, controls = t.controls, newFocus;
10873 
10874 				if (!focussedId)
10875 					return;
10876 
10877 				each(items, function(item, index) {
10878 					if (item.id === focussedId) {
10879 						idx = index;
10880 						return false;
10881 					}
10882 				});
10883 
10884 				idx += dir;
10885 				if (idx < 0) {
10886 					idx = items.length - 1;
10887 				} else if (idx >= items.length) {
10888 					idx = 0;
10889 				}
10890 				
10891 				newFocus = items[idx];
10892 				dom.setAttrib(focussedId, 'tabindex', '-1');
10893 				dom.setAttrib(newFocus.id, 'tabindex', '0');
10894 				dom.get(newFocus.id).focus();
10895 
10896 				if (settings.actOnFocus) {
10897 					settings.onAction(newFocus.id);
10898 				}
10899 
10900 				if (evt)
10901 					Event.cancel(evt);
10902 			};
10903 			
10904 			rootKeydown = function(evt) {
10905 				var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
10906 				
10907 				switch (evt.keyCode) {
10908 					case DOM_VK_LEFT:
10909 						if (enableLeftRight) t.moveFocus(-1);
10910 						break;
10911 	
10912 					case DOM_VK_RIGHT:
10913 						if (enableLeftRight) t.moveFocus(1);
10914 						break;
10915 	
10916 					case DOM_VK_UP:
10917 						if (enableUpDown) t.moveFocus(-1);
10918 						break;
10919 
10920 					case DOM_VK_DOWN:
10921 						if (enableUpDown) t.moveFocus(1);
10922 						break;
10923 
10924 					case DOM_VK_ESCAPE:
10925 						if (settings.onCancel) {
10926 							settings.onCancel();
10927 							Event.cancel(evt);
10928 						}
10929 						break;
10930 
10931 					case DOM_VK_ENTER:
10932 					case DOM_VK_RETURN:
10933 					case DOM_VK_SPACE:
10934 						if (settings.onAction) {
10935 							settings.onAction(focussedId);
10936 							Event.cancel(evt);
10937 						}
10938 						break;
10939 				}
10940 			};
10941 
10942 			// Set up state and listeners for each item.
10943 			each(items, function(item, idx) {
10944 				var tabindex, elm;
10945 
10946 				if (!item.id) {
10947 					item.id = dom.uniqueId('_mce_item_');
10948 				}
10949 
10950 				elm = dom.get(item.id);
10951 
10952 				if (excludeFromTabOrder) {
10953 					dom.bind(elm, 'blur', itemBlurred);
10954 					tabindex = '-1';
10955 				} else {
10956 					tabindex = (idx === 0 ? '0' : '-1');
10957 				}
10958 
10959 				elm.setAttribute('tabindex', tabindex);
10960 				dom.bind(elm, 'focus', itemFocussed);
10961 			});
10962 			
10963 			// Setup initial state for root element.
10964 			if (items[0]){
10965 				focussedId = items[0].id;
10966 			}
10967 
10968 			dom.setAttrib(root, 'tabindex', '-1');
10969 
10970 			// Setup listeners for root element.
10971 			var rootElm = dom.get(root);
10972 			dom.bind(rootElm, 'focus', rootFocussed);
10973 			dom.bind(rootElm, 'keydown', rootKeydown);
10974 		}
10975 	});
10976 })(tinymce);
10977 
10978 (function(tinymce) {
10979 	// Shorten class names
10980 	var DOM = tinymce.DOM, is = tinymce.is;
10981 
10982 	tinymce.create('tinymce.ui.Control', {
10983 		Control : function(id, s, editor) {
10984 			this.id = id;
10985 			this.settings = s = s || {};
10986 			this.rendered = false;
10987 			this.onRender = new tinymce.util.Dispatcher(this);
10988 			this.classPrefix = '';
10989 			this.scope = s.scope || this;
10990 			this.disabled = 0;
10991 			this.active = 0;
10992 			this.editor = editor;
10993 		},
10994 		
10995 		setAriaProperty : function(property, value) {
10996 			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
10997 			if (element) {
10998 				DOM.setAttrib(element, 'aria-' + property, !!value);
10999 			}
11000 		},
11001 		
11002 		focus : function() {
11003 			DOM.get(this.id).focus();
11004 		},
11005 
11006 		setDisabled : function(s) {
11007 			if (s != this.disabled) {
11008 				this.setAriaProperty('disabled', s);
11009 
11010 				this.setState('Disabled', s);
11011 				this.setState('Enabled', !s);
11012 				this.disabled = s;
11013 			}
11014 		},
11015 
11016 		isDisabled : function() {
11017 			return this.disabled;
11018 		},
11019 
11020 		setActive : function(s) {
11021 			if (s != this.active) {
11022 				this.setState('Active', s);
11023 				this.active = s;
11024 				this.setAriaProperty('pressed', s);
11025 			}
11026 		},
11027 
11028 		isActive : function() {
11029 			return this.active;
11030 		},
11031 
11032 		setState : function(c, s) {
11033 			var n = DOM.get(this.id);
11034 
11035 			c = this.classPrefix + c;
11036 
11037 			if (s)
11038 				DOM.addClass(n, c);
11039 			else
11040 				DOM.removeClass(n, c);
11041 		},
11042 
11043 		isRendered : function() {
11044 			return this.rendered;
11045 		},
11046 
11047 		renderHTML : function() {
11048 		},
11049 
11050 		renderTo : function(n) {
11051 			DOM.setHTML(n, this.renderHTML());
11052 		},
11053 
11054 		postRender : function() {
11055 			var t = this, b;
11056 
11057 			// Set pending states
11058 			if (is(t.disabled)) {
11059 				b = t.disabled;
11060 				t.disabled = -1;
11061 				t.setDisabled(b);
11062 			}
11063 
11064 			if (is(t.active)) {
11065 				b = t.active;
11066 				t.active = -1;
11067 				t.setActive(b);
11068 			}
11069 		},
11070 
11071 		remove : function() {
11072 			DOM.remove(this.id);
11073 			this.destroy();
11074 		},
11075 
11076 		destroy : function() {
11077 			tinymce.dom.Event.clear(this.id);
11078 		}
11079 	});
11080 })(tinymce);
11081 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
11082 	Container : function(id, s, editor) {
11083 		this.parent(id, s, editor);
11084 
11085 		this.controls = [];
11086 
11087 		this.lookup = {};
11088 	},
11089 
11090 	add : function(c) {
11091 		this.lookup[c.id] = c;
11092 		this.controls.push(c);
11093 
11094 		return c;
11095 	},
11096 
11097 	get : function(n) {
11098 		return this.lookup[n];
11099 	}
11100 });
11101 
11102 
11103 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11104 	Separator : function(id, s) {
11105 		this.parent(id, s);
11106 		this.classPrefix = 'mceSeparator';
11107 		this.setDisabled(true);
11108 	},
11109 
11110 	renderHTML : function() {
11111 		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
11112 	}
11113 });
11114 
11115 (function(tinymce) {
11116 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11117 
11118 	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
11119 		MenuItem : function(id, s) {
11120 			this.parent(id, s);
11121 			this.classPrefix = 'mceMenuItem';
11122 		},
11123 
11124 		setSelected : function(s) {
11125 			this.setState('Selected', s);
11126 			this.setAriaProperty('checked', !!s);
11127 			this.selected = s;
11128 		},
11129 
11130 		isSelected : function() {
11131 			return this.selected;
11132 		},
11133 
11134 		postRender : function() {
11135 			var t = this;
11136 			
11137 			t.parent();
11138 
11139 			// Set pending state
11140 			if (is(t.selected))
11141 				t.setSelected(t.selected);
11142 		}
11143 	});
11144 })(tinymce);
11145 
11146 (function(tinymce) {
11147 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11148 
11149 	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
11150 		Menu : function(id, s) {
11151 			var t = this;
11152 
11153 			t.parent(id, s);
11154 			t.items = {};
11155 			t.collapsed = false;
11156 			t.menuCount = 0;
11157 			t.onAddItem = new tinymce.util.Dispatcher(this);
11158 		},
11159 
11160 		expand : function(d) {
11161 			var t = this;
11162 
11163 			if (d) {
11164 				walk(t, function(o) {
11165 					if (o.expand)
11166 						o.expand();
11167 				}, 'items', t);
11168 			}
11169 
11170 			t.collapsed = false;
11171 		},
11172 
11173 		collapse : function(d) {
11174 			var t = this;
11175 
11176 			if (d) {
11177 				walk(t, function(o) {
11178 					if (o.collapse)
11179 						o.collapse();
11180 				}, 'items', t);
11181 			}
11182 
11183 			t.collapsed = true;
11184 		},
11185 
11186 		isCollapsed : function() {
11187 			return this.collapsed;
11188 		},
11189 
11190 		add : function(o) {
11191 			if (!o.settings)
11192 				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
11193 
11194 			this.onAddItem.dispatch(this, o);
11195 
11196 			return this.items[o.id] = o;
11197 		},
11198 
11199 		addSeparator : function() {
11200 			return this.add({separator : true});
11201 		},
11202 
11203 		addMenu : function(o) {
11204 			if (!o.collapse)
11205 				o = this.createMenu(o);
11206 
11207 			this.menuCount++;
11208 
11209 			return this.add(o);
11210 		},
11211 
11212 		hasMenus : function() {
11213 			return this.menuCount !== 0;
11214 		},
11215 
11216 		remove : function(o) {
11217 			delete this.items[o.id];
11218 		},
11219 
11220 		removeAll : function() {
11221 			var t = this;
11222 
11223 			walk(t, function(o) {
11224 				if (o.removeAll)
11225 					o.removeAll();
11226 				else
11227 					o.remove();
11228 
11229 				o.destroy();
11230 			}, 'items', t);
11231 
11232 			t.items = {};
11233 		},
11234 
11235 		createMenu : function(o) {
11236 			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
11237 
11238 			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
11239 
11240 			return m;
11241 		}
11242 	});
11243 })(tinymce);
11244 (function(tinymce) {
11245 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
11246 
11247 	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
11248 		DropMenu : function(id, s) {
11249 			s = s || {};
11250 			s.container = s.container || DOM.doc.body;
11251 			s.offset_x = s.offset_x || 0;
11252 			s.offset_y = s.offset_y || 0;
11253 			s.vp_offset_x = s.vp_offset_x || 0;
11254 			s.vp_offset_y = s.vp_offset_y || 0;
11255 
11256 			if (is(s.icons) && !s.icons)
11257 				s['class'] += ' mceNoIcons';
11258 
11259 			this.parent(id, s);
11260 			this.onShowMenu = new tinymce.util.Dispatcher(this);
11261 			this.onHideMenu = new tinymce.util.Dispatcher(this);
11262 			this.classPrefix = 'mceMenu';
11263 		},
11264 
11265 		createMenu : function(s) {
11266 			var t = this, cs = t.settings, m;
11267 
11268 			s.container = s.container || cs.container;
11269 			s.parent = t;
11270 			s.constrain = s.constrain || cs.constrain;
11271 			s['class'] = s['class'] || cs['class'];
11272 			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
11273 			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
11274 			s.keyboard_focus = cs.keyboard_focus;
11275 			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
11276 
11277 			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
11278 
11279 			return m;
11280 		},
11281 		
11282 		focus : function() {
11283 			var t = this;
11284 			if (t.keyboardNav) {
11285 				t.keyboardNav.focus();
11286 			}
11287 		},
11288 
11289 		update : function() {
11290 			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
11291 
11292 			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11293 			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
11294 
11295 			if (!DOM.boxModel)
11296 				t.element.setStyles({width : tw + 2, height : th + 2});
11297 			else
11298 				t.element.setStyles({width : tw, height : th});
11299 
11300 			if (s.max_width)
11301 				DOM.setStyle(co, 'width', tw);
11302 
11303 			if (s.max_height) {
11304 				DOM.setStyle(co, 'height', th);
11305 
11306 				if (tb.clientHeight < s.max_height)
11307 					DOM.setStyle(co, 'overflow', 'hidden');
11308 			}
11309 		},
11310 
11311 		showMenu : function(x, y, px) {
11312 			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
11313 
11314 			t.collapse(1);
11315 
11316 			if (t.isMenuVisible)
11317 				return;
11318 
11319 			if (!t.rendered) {
11320 				co = DOM.add(t.settings.container, t.renderNode());
11321 
11322 				each(t.items, function(o) {
11323 					o.postRender();
11324 				});
11325 
11326 				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11327 			} else
11328 				co = DOM.get('menu_' + t.id);
11329 
11330 			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
11331 			if (!tinymce.isOpera)
11332 				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
11333 
11334 			DOM.show(co);
11335 			t.update();
11336 
11337 			x += s.offset_x || 0;
11338 			y += s.offset_y || 0;
11339 			vp.w -= 4;
11340 			vp.h -= 4;
11341 
11342 			// Move inside viewport if not submenu
11343 			if (s.constrain) {
11344 				w = co.clientWidth - ot;
11345 				h = co.clientHeight - ot;
11346 				mx = vp.x + vp.w;
11347 				my = vp.y + vp.h;
11348 
11349 				if ((x + s.vp_offset_x + w) > mx)
11350 					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
11351 
11352 				if ((y + s.vp_offset_y + h) > my)
11353 					y = Math.max(0, (my - s.vp_offset_y) - h);
11354 			}
11355 
11356 			DOM.setStyles(co, {left : x , top : y});
11357 			t.element.update();
11358 
11359 			t.isMenuVisible = 1;
11360 			t.mouseClickFunc = Event.add(co, 'click', function(e) {
11361 				var m;
11362 
11363 				e = e.target;
11364 
11365 				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
11366 					m = t.items[e.id];
11367 
11368 					if (m.isDisabled())
11369 						return;
11370 
11371 					dm = t;
11372 
11373 					while (dm) {
11374 						if (dm.hideMenu)
11375 							dm.hideMenu();
11376 
11377 						dm = dm.settings.parent;
11378 					}
11379 
11380 					if (m.settings.onclick)
11381 						m.settings.onclick(e);
11382 
11383 					return false; // Cancel to fix onbeforeunload problem
11384 				}
11385 			});
11386 
11387 			if (t.hasMenus()) {
11388 				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
11389 					var m, r, mi;
11390 
11391 					e = e.target;
11392 					if (e && (e = DOM.getParent(e, 'tr'))) {
11393 						m = t.items[e.id];
11394 
11395 						if (t.lastMenu)
11396 							t.lastMenu.collapse(1);
11397 
11398 						if (m.isDisabled())
11399 							return;
11400 
11401 						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
11402 							//p = DOM.getPos(s.container);
11403 							r = DOM.getRect(e);
11404 							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
11405 							t.lastMenu = m;
11406 							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
11407 						}
11408 					}
11409 				});
11410 			}
11411 			
11412 			Event.add(co, 'keydown', t._keyHandler, t);
11413 
11414 			t.onShowMenu.dispatch(t);
11415 
11416 			if (s.keyboard_focus) { 
11417 				t._setupKeyboardNav(); 
11418 			}
11419 		},
11420 
11421 		hideMenu : function(c) {
11422 			var t = this, co = DOM.get('menu_' + t.id), e;
11423 
11424 			if (!t.isMenuVisible)
11425 				return;
11426 
11427 			if (t.keyboardNav) t.keyboardNav.destroy();
11428 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11429 			Event.remove(co, 'click', t.mouseClickFunc);
11430 			Event.remove(co, 'keydown', t._keyHandler);
11431 			DOM.hide(co);
11432 			t.isMenuVisible = 0;
11433 
11434 			if (!c)
11435 				t.collapse(1);
11436 
11437 			if (t.element)
11438 				t.element.hide();
11439 
11440 			if (e = DOM.get(t.id))
11441 				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
11442 
11443 			t.onHideMenu.dispatch(t);
11444 		},
11445 
11446 		add : function(o) {
11447 			var t = this, co;
11448 
11449 			o = t.parent(o);
11450 
11451 			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
11452 				t._add(DOM.select('tbody', co)[0], o);
11453 
11454 			return o;
11455 		},
11456 
11457 		collapse : function(d) {
11458 			this.parent(d);
11459 			this.hideMenu(1);
11460 		},
11461 
11462 		remove : function(o) {
11463 			DOM.remove(o.id);
11464 			this.destroy();
11465 
11466 			return this.parent(o);
11467 		},
11468 
11469 		destroy : function() {
11470 			var t = this, co = DOM.get('menu_' + t.id);
11471 
11472 			if (t.keyboardNav) t.keyboardNav.destroy();
11473 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11474 			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
11475 			Event.remove(co, 'click', t.mouseClickFunc);
11476 			Event.remove(co, 'keydown', t._keyHandler);
11477 
11478 			if (t.element)
11479 				t.element.remove();
11480 
11481 			DOM.remove(co);
11482 		},
11483 
11484 		renderNode : function() {
11485 			var t = this, s = t.settings, n, tb, co, w;
11486 
11487 			w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
11488 			if (t.settings.parent) {
11489 				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
11490 			}
11491 			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
11492 			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11493 
11494 			if (s.menu_line)
11495 				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
11496 
11497 //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
11498 			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
11499 			tb = DOM.add(n, 'tbody');
11500 
11501 			each(t.items, function(o) {
11502 				t._add(tb, o);
11503 			});
11504 
11505 			t.rendered = true;
11506 
11507 			return w;
11508 		},
11509 
11510 		// Internal functions
11511 		_setupKeyboardNav : function(){
11512 			var contextMenu, menuItems, t=this; 
11513 			contextMenu = DOM.get('menu_' + t.id);
11514 			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
11515 			menuItems.splice(0,0,contextMenu);
11516 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11517 				root: 'menu_' + t.id,
11518 				items: menuItems,
11519 				onCancel: function() {
11520 					t.hideMenu();
11521 				},
11522 				enableUpDown: true
11523 			});
11524 			contextMenu.focus();
11525 		},
11526 
11527 		_keyHandler : function(evt) {
11528 			var t = this, e;
11529 			switch (evt.keyCode) {
11530 				case 37: // Left
11531 					if (t.settings.parent) {
11532 						t.hideMenu();
11533 						t.settings.parent.focus();
11534 						Event.cancel(evt);
11535 					}
11536 					break;
11537 				case 39: // Right
11538 					if (t.mouseOverFunc)
11539 						t.mouseOverFunc(evt);
11540 					break;
11541 			}
11542 		},
11543 
11544 		_add : function(tb, o) {
11545 			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
11546 
11547 			if (s.separator) {
11548 				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
11549 				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
11550 
11551 				if (n = ro.previousSibling)
11552 					DOM.addClass(n, 'mceLast');
11553 
11554 				return;
11555 			}
11556 
11557 			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
11558 			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
11559 			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
11560 
11561 			if (s.parent) {
11562 				DOM.setAttrib(a, 'aria-haspopup', 'true');
11563 				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
11564 			}
11565 
11566 			DOM.addClass(it, s['class']);
11567 //			n = DOM.add(n, 'span', {'class' : 'item'});
11568 
11569 			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
11570 
11571 			if (s.icon_src)
11572 				DOM.add(ic, 'img', {src : s.icon_src});
11573 
11574 			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
11575 
11576 			if (o.settings.style) {
11577 				if (typeof o.settings.style == "function")
11578 					o.settings.style = o.settings.style();
11579 
11580 				DOM.setAttrib(n, 'style', o.settings.style);
11581 			}
11582 
11583 			if (tb.childNodes.length == 1)
11584 				DOM.addClass(ro, 'mceFirst');
11585 
11586 			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
11587 				DOM.addClass(ro, 'mceFirst');
11588 
11589 			if (o.collapse)
11590 				DOM.addClass(ro, cp + 'ItemSub');
11591 
11592 			if (n = ro.previousSibling)
11593 				DOM.removeClass(n, 'mceLast');
11594 
11595 			DOM.addClass(ro, 'mceLast');
11596 		}
11597 	});
11598 })(tinymce);
11599 (function(tinymce) {
11600 	var DOM = tinymce.DOM;
11601 
11602 	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
11603 		Button : function(id, s, ed) {
11604 			this.parent(id, s, ed);
11605 			this.classPrefix = 'mceButton';
11606 		},
11607 
11608 		renderHTML : function() {
11609 			var cp = this.classPrefix, s = this.settings, h, l;
11610 
11611 			l = DOM.encode(s.label || '');
11612 			h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
11613 			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
11614 				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11615 			else
11616 				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11617 
11618 			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
11619 			h += '</a>';
11620 			return h;
11621 		},
11622 
11623 		postRender : function() {
11624 			var t = this, s = t.settings, imgBookmark;
11625 
11626 			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
11627 			// need to keep the selection in case the selection is lost
11628 			if (tinymce.isIE && t.editor) {
11629 				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
11630 					var nodeName = t.editor.selection.getNode().nodeName;
11631 					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
11632 				});
11633 			}
11634 			tinymce.dom.Event.add(t.id, 'click', function(e) {
11635 				if (!t.isDisabled()) {
11636 					// restore the selection in case the selection is lost in IE
11637 					if (tinymce.isIE && t.editor && imgBookmark !== null) {
11638 						t.editor.selection.moveToBookmark(imgBookmark);
11639 					}
11640 					return s.onclick.call(s.scope, e);
11641 				}
11642 			});
11643 			tinymce.dom.Event.add(t.id, 'keyup', function(e) {
11644 				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
11645 					return s.onclick.call(s.scope, e);
11646 			});
11647 		}
11648 	});
11649 })(tinymce);
11650 
11651 (function(tinymce) {
11652 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11653 
11654 	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
11655 		ListBox : function(id, s, ed) {
11656 			var t = this;
11657 
11658 			t.parent(id, s, ed);
11659 
11660 			t.items = [];
11661 
11662 			t.onChange = new Dispatcher(t);
11663 
11664 			t.onPostRender = new Dispatcher(t);
11665 
11666 			t.onAdd = new Dispatcher(t);
11667 
11668 			t.onRenderMenu = new tinymce.util.Dispatcher(this);
11669 
11670 			t.classPrefix = 'mceListBox';
11671 			t.marked = {};
11672 		},
11673 
11674 		select : function(va) {
11675 			var t = this, fv, f;
11676 
11677 			t.marked = {};
11678 
11679 			if (va == undef)
11680 				return t.selectByIndex(-1);
11681 
11682 			// Is string or number make function selector
11683 			if (va && typeof(va)=="function")
11684 				f = va;
11685 			else {
11686 				f = function(v) {
11687 					return v == va;
11688 				};
11689 			}
11690 
11691 			// Do we need to do something?
11692 			if (va != t.selectedValue) {
11693 				// Find item
11694 				each(t.items, function(o, i) {
11695 					if (f(o.value)) {
11696 						fv = 1;
11697 						t.selectByIndex(i);
11698 						return false;
11699 					}
11700 				});
11701 
11702 				if (!fv)
11703 					t.selectByIndex(-1);
11704 			}
11705 		},
11706 
11707 		selectByIndex : function(idx) {
11708 			var t = this, e, o, label;
11709 
11710 			t.marked = {};
11711 
11712 			if (idx != t.selectedIndex) {
11713 				e = DOM.get(t.id + '_text');
11714 				label = DOM.get(t.id + '_voiceDesc');
11715 				o = t.items[idx];
11716 
11717 				if (o) {
11718 					t.selectedValue = o.value;
11719 					t.selectedIndex = idx;
11720 					DOM.setHTML(e, DOM.encode(o.title));
11721 					DOM.setHTML(label, t.settings.title + " - " + o.title);
11722 					DOM.removeClass(e, 'mceTitle');
11723 					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
11724 				} else {
11725 					DOM.setHTML(e, DOM.encode(t.settings.title));
11726 					DOM.setHTML(label, DOM.encode(t.settings.title));
11727 					DOM.addClass(e, 'mceTitle');
11728 					t.selectedValue = t.selectedIndex = null;
11729 					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
11730 				}
11731 				e = 0;
11732 			}
11733 		},
11734 
11735 		mark : function(value) {
11736 			this.marked[value] = true;
11737 		},
11738 
11739 		add : function(n, v, o) {
11740 			var t = this;
11741 
11742 			o = o || {};
11743 			o = tinymce.extend(o, {
11744 				title : n,
11745 				value : v
11746 			});
11747 
11748 			t.items.push(o);
11749 			t.onAdd.dispatch(t, o);
11750 		},
11751 
11752 		getLength : function() {
11753 			return this.items.length;
11754 		},
11755 
11756 		renderHTML : function() {
11757 			var h = '', t = this, s = t.settings, cp = t.classPrefix;
11758 
11759 			h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
11760 			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
11761 			h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
11762 			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
11763 			h += '</tr></tbody></table></span>';
11764 
11765 			return h;
11766 		},
11767 
11768 		showMenu : function() {
11769 			var t = this, p2, e = DOM.get(this.id), m;
11770 
11771 			if (t.isDisabled() || t.items.length === 0)
11772 				return;
11773 
11774 			if (t.menu && t.menu.isMenuVisible)
11775 				return t.hideMenu();
11776 
11777 			if (!t.isMenuRendered) {
11778 				t.renderMenu();
11779 				t.isMenuRendered = true;
11780 			}
11781 
11782 			p2 = DOM.getPos(e);
11783 
11784 			m = t.menu;
11785 			m.settings.offset_x = p2.x;
11786 			m.settings.offset_y = p2.y;
11787 			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
11788 
11789 			// Select in menu
11790 			each(t.items, function(o) {
11791 				if (m.items[o.id]) {
11792 					m.items[o.id].setSelected(0);
11793 				}
11794 			});
11795 
11796 			each(t.items, function(o) {
11797 				if (m.items[o.id] && t.marked[o.value]) {
11798 					m.items[o.id].setSelected(1);
11799 				}
11800 
11801 				if (o.value === t.selectedValue) {
11802 					m.items[o.id].setSelected(1);
11803 				}
11804 			});
11805 
11806 			m.showMenu(0, e.clientHeight);
11807 
11808 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11809 			DOM.addClass(t.id, t.classPrefix + 'Selected');
11810 
11811 			//DOM.get(t.id + '_text').focus();
11812 		},
11813 
11814 		hideMenu : function(e) {
11815 			var t = this;
11816 
11817 			if (t.menu && t.menu.isMenuVisible) {
11818 				DOM.removeClass(t.id, t.classPrefix + 'Selected');
11819 
11820 				// Prevent double toogles by canceling the mouse click event to the button
11821 				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
11822 					return;
11823 
11824 				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
11825 					DOM.removeClass(t.id, t.classPrefix + 'Selected');
11826 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11827 					t.menu.hideMenu();
11828 				}
11829 			}
11830 		},
11831 
11832 		renderMenu : function() {
11833 			var t = this, m;
11834 
11835 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
11836 				menu_line : 1,
11837 				'class' : t.classPrefix + 'Menu mceNoIcons',
11838 				max_width : 250,
11839 				max_height : 150
11840 			});
11841 
11842 			m.onHideMenu.add(function() {
11843 				t.hideMenu();
11844 				t.focus();
11845 			});
11846 
11847 			m.add({
11848 				title : t.settings.title,
11849 				'class' : 'mceMenuItemTitle',
11850 				onclick : function() {
11851 					if (t.settings.onselect('') !== false)
11852 						t.select(''); // Must be runned after
11853 				}
11854 			});
11855 
11856 			each(t.items, function(o) {
11857 				// No value then treat it as a title
11858 				if (o.value === undef) {
11859 					m.add({
11860 						title : o.title,
11861 						role : "option",
11862 						'class' : 'mceMenuItemTitle',
11863 						onclick : function() {
11864 							if (t.settings.onselect('') !== false)
11865 								t.select(''); // Must be runned after
11866 						}
11867 					});
11868 				} else {
11869 					o.id = DOM.uniqueId();
11870 					o.role= "option";
11871 					o.onclick = function() {
11872 						if (t.settings.onselect(o.value) !== false)
11873 							t.select(o.value); // Must be runned after
11874 					};
11875 
11876 					m.add(o);
11877 				}
11878 			});
11879 
11880 			t.onRenderMenu.dispatch(t, m);
11881 			t.menu = m;
11882 		},
11883 
11884 		postRender : function() {
11885 			var t = this, cp = t.classPrefix;
11886 
11887 			Event.add(t.id, 'click', t.showMenu, t);
11888 			Event.add(t.id, 'keydown', function(evt) {
11889 				if (evt.keyCode == 32) { // Space
11890 					t.showMenu(evt);
11891 					Event.cancel(evt);
11892 				}
11893 			});
11894 			Event.add(t.id, 'focus', function() {
11895 				if (!t._focused) {
11896 					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
11897 						if (e.keyCode == 40) {
11898 							t.showMenu();
11899 							Event.cancel(e);
11900 						}
11901 					});
11902 					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
11903 						var v;
11904 						if (e.keyCode == 13) {
11905 							// Fake select on enter
11906 							v = t.selectedValue;
11907 							t.selectedValue = null; // Needs to be null to fake change
11908 							Event.cancel(e);
11909 							t.settings.onselect(v);
11910 						}
11911 					});
11912 				}
11913 
11914 				t._focused = 1;
11915 			});
11916 			Event.add(t.id, 'blur', function() {
11917 				Event.remove(t.id, 'keydown', t.keyDownHandler);
11918 				Event.remove(t.id, 'keypress', t.keyPressHandler);
11919 				t._focused = 0;
11920 			});
11921 
11922 			// Old IE doesn't have hover on all elements
11923 			if (tinymce.isIE6 || !DOM.boxModel) {
11924 				Event.add(t.id, 'mouseover', function() {
11925 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11926 						DOM.addClass(t.id, cp + 'Hover');
11927 				});
11928 
11929 				Event.add(t.id, 'mouseout', function() {
11930 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11931 						DOM.removeClass(t.id, cp + 'Hover');
11932 				});
11933 			}
11934 
11935 			t.onPostRender.dispatch(t, DOM.get(t.id));
11936 		},
11937 
11938 		destroy : function() {
11939 			this.parent();
11940 
11941 			Event.clear(this.id + '_text');
11942 			Event.clear(this.id + '_open');
11943 		}
11944 	});
11945 })(tinymce);
11946 
11947 (function(tinymce) {
11948 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11949 
11950 	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
11951 		NativeListBox : function(id, s) {
11952 			this.parent(id, s);
11953 			this.classPrefix = 'mceNativeListBox';
11954 		},
11955 
11956 		setDisabled : function(s) {
11957 			DOM.get(this.id).disabled = s;
11958 			this.setAriaProperty('disabled', s);
11959 		},
11960 
11961 		isDisabled : function() {
11962 			return DOM.get(this.id).disabled;
11963 		},
11964 
11965 		select : function(va) {
11966 			var t = this, fv, f;
11967 
11968 			if (va == undef)
11969 				return t.selectByIndex(-1);
11970 
11971 			// Is string or number make function selector
11972 			if (va && typeof(va)=="function")
11973 				f = va;
11974 			else {
11975 				f = function(v) {
11976 					return v == va;
11977 				};
11978 			}
11979 
11980 			// Do we need to do something?
11981 			if (va != t.selectedValue) {
11982 				// Find item
11983 				each(t.items, function(o, i) {
11984 					if (f(o.value)) {
11985 						fv = 1;
11986 						t.selectByIndex(i);
11987 						return false;
11988 					}
11989 				});
11990 
11991 				if (!fv)
11992 					t.selectByIndex(-1);
11993 			}
11994 		},
11995 
11996 		selectByIndex : function(idx) {
11997 			DOM.get(this.id).selectedIndex = idx + 1;
11998 			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
11999 		},
12000 
12001 		add : function(n, v, a) {
12002 			var o, t = this;
12003 
12004 			a = a || {};
12005 			a.value = v;
12006 
12007 			if (t.isRendered())
12008 				DOM.add(DOM.get(this.id), 'option', a, n);
12009 
12010 			o = {
12011 				title : n,
12012 				value : v,
12013 				attribs : a
12014 			};
12015 
12016 			t.items.push(o);
12017 			t.onAdd.dispatch(t, o);
12018 		},
12019 
12020 		getLength : function() {
12021 			return this.items.length;
12022 		},
12023 
12024 		renderHTML : function() {
12025 			var h, t = this;
12026 
12027 			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
12028 
12029 			each(t.items, function(it) {
12030 				h += DOM.createHTML('option', {value : it.value}, it.title);
12031 			});
12032 
12033 			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
12034 			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
12035 			return h;
12036 		},
12037 
12038 		postRender : function() {
12039 			var t = this, ch, changeListenerAdded = true;
12040 
12041 			t.rendered = true;
12042 
12043 			function onChange(e) {
12044 				var v = t.items[e.target.selectedIndex - 1];
12045 
12046 				if (v && (v = v.value)) {
12047 					t.onChange.dispatch(t, v);
12048 
12049 					if (t.settings.onselect)
12050 						t.settings.onselect(v);
12051 				}
12052 			};
12053 
12054 			Event.add(t.id, 'change', onChange);
12055 
12056 			// Accessibility keyhandler
12057 			Event.add(t.id, 'keydown', function(e) {
12058 				var bf;
12059 
12060 				Event.remove(t.id, 'change', ch);
12061 				changeListenerAdded = false;
12062 
12063 				bf = Event.add(t.id, 'blur', function() {
12064 					if (changeListenerAdded) return;
12065 					changeListenerAdded = true;
12066 					Event.add(t.id, 'change', onChange);
12067 					Event.remove(t.id, 'blur', bf);
12068 				});
12069 
12070 				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
12071 				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
12072 					return Event.prevent(e);
12073 				}
12074 				
12075 				if (e.keyCode == 13 || e.keyCode == 32) {
12076 					onChange(e);
12077 					return Event.cancel(e);
12078 				}
12079 			});
12080 
12081 			t.onPostRender.dispatch(t, DOM.get(t.id));
12082 		}
12083 	});
12084 })(tinymce);
12085 
12086 (function(tinymce) {
12087 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12088 
12089 	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
12090 		MenuButton : function(id, s, ed) {
12091 			this.parent(id, s, ed);
12092 
12093 			this.onRenderMenu = new tinymce.util.Dispatcher(this);
12094 
12095 			s.menu_container = s.menu_container || DOM.doc.body;
12096 		},
12097 
12098 		showMenu : function() {
12099 			var t = this, p1, p2, e = DOM.get(t.id), m;
12100 
12101 			if (t.isDisabled())
12102 				return;
12103 
12104 			if (!t.isMenuRendered) {
12105 				t.renderMenu();
12106 				t.isMenuRendered = true;
12107 			}
12108 
12109 			if (t.isMenuVisible)
12110 				return t.hideMenu();
12111 
12112 			p1 = DOM.getPos(t.settings.menu_container);
12113 			p2 = DOM.getPos(e);
12114 
12115 			m = t.menu;
12116 			m.settings.offset_x = p2.x;
12117 			m.settings.offset_y = p2.y;
12118 			m.settings.vp_offset_x = p2.x;
12119 			m.settings.vp_offset_y = p2.y;
12120 			m.settings.keyboard_focus = t._focused;
12121 			m.showMenu(0, e.firstChild.clientHeight);
12122 
12123 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12124 			t.setState('Selected', 1);
12125 
12126 			t.isMenuVisible = 1;
12127 		},
12128 
12129 		renderMenu : function() {
12130 			var t = this, m;
12131 
12132 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12133 				menu_line : 1,
12134 				'class' : this.classPrefix + 'Menu',
12135 				icons : t.settings.icons
12136 			});
12137 
12138 			m.onHideMenu.add(function() {
12139 				t.hideMenu();
12140 				t.focus();
12141 			});
12142 
12143 			t.onRenderMenu.dispatch(t, m);
12144 			t.menu = m;
12145 		},
12146 
12147 		hideMenu : function(e) {
12148 			var t = this;
12149 
12150 			// Prevent double toogles by canceling the mouse click event to the button
12151 			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
12152 				return;
12153 
12154 			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12155 				t.setState('Selected', 0);
12156 				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12157 				if (t.menu)
12158 					t.menu.hideMenu();
12159 			}
12160 
12161 			t.isMenuVisible = 0;
12162 		},
12163 
12164 		postRender : function() {
12165 			var t = this, s = t.settings;
12166 
12167 			Event.add(t.id, 'click', function() {
12168 				if (!t.isDisabled()) {
12169 					if (s.onclick)
12170 						s.onclick(t.value);
12171 
12172 					t.showMenu();
12173 				}
12174 			});
12175 		}
12176 	});
12177 })(tinymce);
12178 
12179 (function(tinymce) {
12180 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12181 
12182 	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
12183 		SplitButton : function(id, s, ed) {
12184 			this.parent(id, s, ed);
12185 			this.classPrefix = 'mceSplitButton';
12186 		},
12187 
12188 		renderHTML : function() {
12189 			var h, t = this, s = t.settings, h1;
12190 
12191 			h = '<tbody><tr>';
12192 
12193 			if (s.image)
12194 				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
12195 			else
12196 				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
12197 
12198 			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
12199 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12200 	
12201 			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
12202 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12203 
12204 			h += '</tr></tbody>';
12205 			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
12206 			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
12207 		},
12208 
12209 		postRender : function() {
12210 			var t = this, s = t.settings, activate;
12211 
12212 			if (s.onclick) {
12213 				activate = function(evt) {
12214 					if (!t.isDisabled()) {
12215 						s.onclick(t.value);
12216 						Event.cancel(evt);
12217 					}
12218 				};
12219 				Event.add(t.id + '_action', 'click', activate);
12220 				Event.add(t.id, ['click', 'keydown'], function(evt) {
12221 					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
12222 					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
12223 						activate();
12224 						Event.cancel(evt);
12225 					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
12226 						t.showMenu();
12227 						Event.cancel(evt);
12228 					}
12229 				});
12230 			}
12231 
12232 			Event.add(t.id + '_open', 'click', function (evt) {
12233 				t.showMenu();
12234 				Event.cancel(evt);
12235 			});
12236 			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
12237 			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
12238 
12239 			// Old IE doesn't have hover on all elements
12240 			if (tinymce.isIE6 || !DOM.boxModel) {
12241 				Event.add(t.id, 'mouseover', function() {
12242 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12243 						DOM.addClass(t.id, 'mceSplitButtonHover');
12244 				});
12245 
12246 				Event.add(t.id, 'mouseout', function() {
12247 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12248 						DOM.removeClass(t.id, 'mceSplitButtonHover');
12249 				});
12250 			}
12251 		},
12252 
12253 		destroy : function() {
12254 			this.parent();
12255 
12256 			Event.clear(this.id + '_action');
12257 			Event.clear(this.id + '_open');
12258 			Event.clear(this.id);
12259 		}
12260 	});
12261 })(tinymce);
12262 
12263 (function(tinymce) {
12264 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
12265 
12266 	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
12267 		ColorSplitButton : function(id, s, ed) {
12268 			var t = this;
12269 
12270 			t.parent(id, s, ed);
12271 
12272 			t.settings = s = tinymce.extend({
12273 				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
12274 				grid_width : 8,
12275 				default_color : '#888888'
12276 			}, t.settings);
12277 
12278 			t.onShowMenu = new tinymce.util.Dispatcher(t);
12279 
12280 			t.onHideMenu = new tinymce.util.Dispatcher(t);
12281 
12282 			t.value = s.default_color;
12283 		},
12284 
12285 		showMenu : function() {
12286 			var t = this, r, p, e, p2;
12287 
12288 			if (t.isDisabled())
12289 				return;
12290 
12291 			if (!t.isMenuRendered) {
12292 				t.renderMenu();
12293 				t.isMenuRendered = true;
12294 			}
12295 
12296 			if (t.isMenuVisible)
12297 				return t.hideMenu();
12298 
12299 			e = DOM.get(t.id);
12300 			DOM.show(t.id + '_menu');
12301 			DOM.addClass(e, 'mceSplitButtonSelected');
12302 			p2 = DOM.getPos(e);
12303 			DOM.setStyles(t.id + '_menu', {
12304 				left : p2.x,
12305 				top : p2.y + e.firstChild.clientHeight,
12306 				zIndex : 200000
12307 			});
12308 			e = 0;
12309 
12310 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12311 			t.onShowMenu.dispatch(t);
12312 
12313 			if (t._focused) {
12314 				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
12315 					if (e.keyCode == 27)
12316 						t.hideMenu();
12317 				});
12318 
12319 				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
12320 			}
12321 
12322 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
12323 				root: t.id + '_menu',
12324 				items: DOM.select('a', t.id + '_menu'),
12325 				onCancel: function() {
12326 					t.hideMenu();
12327 					t.focus();
12328 				}
12329 			});
12330 
12331 			t.keyboardNav.focus();
12332 			t.isMenuVisible = 1;
12333 		},
12334 
12335 		hideMenu : function(e) {
12336 			var t = this;
12337 
12338 			if (t.isMenuVisible) {
12339 				// Prevent double toogles by canceling the mouse click event to the button
12340 				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
12341 					return;
12342 
12343 				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
12344 					DOM.removeClass(t.id, 'mceSplitButtonSelected');
12345 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12346 					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
12347 					DOM.hide(t.id + '_menu');
12348 				}
12349 
12350 				t.isMenuVisible = 0;
12351 				t.onHideMenu.dispatch();
12352 				t.keyboardNav.destroy();
12353 			}
12354 		},
12355 
12356 		renderMenu : function() {
12357 			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
12358 
12359 			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
12360 			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
12361 			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
12362 
12363 			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
12364 			tb = DOM.add(n, 'tbody');
12365 
12366 			// Generate color grid
12367 			i = 0;
12368 			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
12369 				c = c.replace(/^#/, '');
12370 
12371 				if (!i--) {
12372 					tr = DOM.add(tb, 'tr');
12373 					i = s.grid_width - 1;
12374 				}
12375 
12376 				n = DOM.add(tr, 'td');
12377 				var settings = {
12378 					href : 'javascript:;',
12379 					style : {
12380 						backgroundColor : '#' + c
12381 					},
12382 					'title': t.editor.getLang('colors.' + c, c),
12383 					'data-mce-color' : '#' + c
12384 				};
12385 
12386 				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
12387 				if (!tinymce.isIE ) {
12388 					settings.role = 'option';
12389 				}
12390 
12391 				n = DOM.add(n, 'a', settings);
12392 
12393 				if (t.editor.forcedHighContrastMode) {
12394 					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
12395 					if (n.getContext && (context = n.getContext("2d"))) {
12396 						context.fillStyle = '#' + c;
12397 						context.fillRect(0, 0, 16, 16);
12398 					} else {
12399 						// No point leaving a canvas element around if it's not supported for drawing on anyway.
12400 						DOM.remove(n);
12401 					}
12402 				}
12403 			});
12404 
12405 			if (s.more_colors_func) {
12406 				n = DOM.add(tb, 'tr');
12407 				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
12408 				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
12409 
12410 				Event.add(n, 'click', function(e) {
12411 					s.more_colors_func.call(s.more_colors_scope || this);
12412 					return Event.cancel(e); // Cancel to fix onbeforeunload problem
12413 				});
12414 			}
12415 
12416 			DOM.addClass(m, 'mceColorSplitMenu');
12417 
12418 			// Prevent IE from scrolling and hindering click to occur #4019
12419 			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
12420 
12421 			Event.add(t.id + '_menu', 'click', function(e) {
12422 				var c;
12423 
12424 				e = DOM.getParent(e.target, 'a', tb);
12425 
12426 				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
12427 					t.setColor(c);
12428 
12429 				return false; // Prevent IE auto save warning
12430 			});
12431 
12432 			return w;
12433 		},
12434 
12435 		setColor : function(c) {
12436 			this.displayColor(c);
12437 			this.hideMenu();
12438 			this.settings.onselect(c);
12439 		},
12440 		
12441 		displayColor : function(c) {
12442 			var t = this;
12443 
12444 			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
12445 
12446 			t.value = c;
12447 		},
12448 
12449 		postRender : function() {
12450 			var t = this, id = t.id;
12451 
12452 			t.parent();
12453 			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
12454 			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
12455 		},
12456 
12457 		destroy : function() {
12458 			var self = this;
12459 
12460 			self.parent();
12461 
12462 			Event.clear(self.id + '_menu');
12463 			Event.clear(self.id + '_more');
12464 			DOM.remove(self.id + '_menu');
12465 
12466 			if (self.keyboardNav) {
12467 				self.keyboardNav.destroy();
12468 			}
12469 		}
12470 	});
12471 })(tinymce);
12472 
12473 (function(tinymce) {
12474 // Shorten class names
12475 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
12476 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
12477 	renderHTML : function() {
12478 		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
12479 
12480 		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
12481 		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
12482 		h.push("<span role='application'>");
12483 		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
12484 		each(controls, function(toolbar) {
12485 			h.push(toolbar.renderHTML());
12486 		});
12487 		h.push("</span>");
12488 		h.push('</div>');
12489 
12490 		return h.join('');
12491 	},
12492 	
12493 	focus : function() {
12494 		var t = this;
12495 		dom.get(t.id).focus();
12496 	},
12497 	
12498 	postRender : function() {
12499 		var t = this, items = [];
12500 
12501 		each(t.controls, function(toolbar) {
12502 			each (toolbar.controls, function(control) {
12503 				if (control.id) {
12504 					items.push(control);
12505 				}
12506 			});
12507 		});
12508 
12509 		t.keyNav = new tinymce.ui.KeyboardNavigation({
12510 			root: t.id,
12511 			items: items,
12512 			onCancel: function() {
12513 				//Move focus if webkit so that navigation back will read the item.
12514 				if (tinymce.isWebKit) {
12515 					dom.get(t.editor.id+"_ifr").focus();
12516 				}
12517 				t.editor.focus();
12518 			},
12519 			excludeFromTabOrder: !t.settings.tab_focus_toolbar
12520 		});
12521 	},
12522 	
12523 	destroy : function() {
12524 		var self = this;
12525 
12526 		self.parent();
12527 		self.keyNav.destroy();
12528 		Event.clear(self.id);
12529 	}
12530 });
12531 })(tinymce);
12532 
12533 (function(tinymce) {
12534 // Shorten class names
12535 var dom = tinymce.DOM, each = tinymce.each;
12536 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12537 	renderHTML : function() {
12538 		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
12539 
12540 		cl = t.controls;
12541 		for (i=0; i<cl.length; i++) {
12542 			// Get current control, prev control, next control and if the control is a list box or not
12543 			co = cl[i];
12544 			pr = cl[i - 1];
12545 			nx = cl[i + 1];
12546 
12547 			// Add toolbar start
12548 			if (i === 0) {
12549 				c = 'mceToolbarStart';
12550 
12551 				if (co.Button)
12552 					c += ' mceToolbarStartButton';
12553 				else if (co.SplitButton)
12554 					c += ' mceToolbarStartSplitButton';
12555 				else if (co.ListBox)
12556 					c += ' mceToolbarStartListBox';
12557 
12558 				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12559 			}
12560 
12561 			// Add toolbar end before list box and after the previous button
12562 			// This is to fix the o2k7 editor skins
12563 			if (pr && co.ListBox) {
12564 				if (pr.Button || pr.SplitButton)
12565 					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
12566 			}
12567 
12568 			// Render control HTML
12569 
12570 			// IE 8 quick fix, needed to propertly generate a hit area for anchors
12571 			if (dom.stdMode)
12572 				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
12573 			else
12574 				h += '<td>' + co.renderHTML() + '</td>';
12575 
12576 			// Add toolbar start after list box and before the next button
12577 			// This is to fix the o2k7 editor skins
12578 			if (nx && co.ListBox) {
12579 				if (nx.Button || nx.SplitButton)
12580 					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
12581 			}
12582 		}
12583 
12584 		c = 'mceToolbarEnd';
12585 
12586 		if (co.Button)
12587 			c += ' mceToolbarEndButton';
12588 		else if (co.SplitButton)
12589 			c += ' mceToolbarEndSplitButton';
12590 		else if (co.ListBox)
12591 			c += ' mceToolbarEndListBox';
12592 
12593 		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12594 
12595 		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
12596 	}
12597 });
12598 })(tinymce);
12599 
12600 (function(tinymce) {
12601 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
12602 
12603 	tinymce.create('tinymce.AddOnManager', {
12604 		AddOnManager : function() {
12605 			var self = this;
12606 
12607 			self.items = [];
12608 			self.urls = {};
12609 			self.lookup = {};
12610 			self.onAdd = new Dispatcher(self);
12611 		},
12612 
12613 		get : function(n) {
12614 			if (this.lookup[n]) {
12615 				return this.lookup[n].instance;
12616 			} else {
12617 				return undefined;
12618 			}
12619 		},
12620 
12621 		dependencies : function(n) {
12622 			var result;
12623 			if (this.lookup[n]) {
12624 				result = this.lookup[n].dependencies;
12625 			}
12626 			return result || [];
12627 		},
12628 
12629 		requireLangPack : function(n) {
12630 			var s = tinymce.settings;
12631 
12632 			if (s && s.language && s.language_load !== false)
12633 				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
12634 		},
12635 
12636 		add : function(id, o, dependencies) {
12637 			this.items.push(o);
12638 			this.lookup[id] = {instance:o, dependencies:dependencies};
12639 			this.onAdd.dispatch(this, id, o);
12640 
12641 			return o;
12642 		},
12643 		createUrl: function(baseUrl, dep) {
12644 			if (typeof dep === "object") {
12645 				return dep
12646 			} else {
12647 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
12648 			}
12649 		},
12650 
12651 		addComponents: function(pluginName, scripts) {
12652 			var pluginUrl = this.urls[pluginName];
12653 			tinymce.each(scripts, function(script){
12654 				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
12655 			});
12656 		},
12657 
12658 		load : function(n, u, cb, s) {
12659 			var t = this, url = u;
12660 
12661 			function loadDependencies() {
12662 				var dependencies = t.dependencies(n);
12663 				tinymce.each(dependencies, function(dep) {
12664 					var newUrl = t.createUrl(u, dep);
12665 					t.load(newUrl.resource, newUrl, undefined, undefined);
12666 				});
12667 				if (cb) {
12668 					if (s) {
12669 						cb.call(s);
12670 					} else {
12671 						cb.call(tinymce.ScriptLoader);
12672 					}
12673 				}
12674 			}
12675 
12676 			if (t.urls[n])
12677 				return;
12678 			if (typeof u === "object")
12679 				url = u.prefix + u.resource + u.suffix;
12680 
12681 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
12682 				url = tinymce.baseURL + '/' + url;
12683 
12684 			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
12685 
12686 			if (t.lookup[n]) {
12687 				loadDependencies();
12688 			} else {
12689 				tinymce.ScriptLoader.add(url, loadDependencies, s);
12690 			}
12691 		}
12692 	});
12693 
12694 	// Create plugin and theme managers
12695 	tinymce.PluginManager = new tinymce.AddOnManager();
12696 	tinymce.ThemeManager = new tinymce.AddOnManager();
12697 }(tinymce));
12698 
12699 (function(tinymce) {
12700 	// Shorten names
12701 	var each = tinymce.each, extend = tinymce.extend,
12702 		DOM = tinymce.DOM, Event = tinymce.dom.Event,
12703 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
12704 		explode = tinymce.explode,
12705 		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
12706 
12707 	// Setup some URLs where the editor API is located and where the document is
12708 	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
12709 	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
12710 		tinymce.documentBaseURL += '/';
12711 
12712 	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
12713 
12714 	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
12715 
12716 	// Add before unload listener
12717 	// This was required since IE was leaking memory if you added and removed beforeunload listeners
12718 	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
12719 	tinymce.onBeforeUnload = new Dispatcher(tinymce);
12720 
12721 	// Must be on window or IE will leak if the editor is placed in frame or iframe
12722 	Event.add(window, 'beforeunload', function(e) {
12723 		tinymce.onBeforeUnload.dispatch(tinymce, e);
12724 	});
12725 
12726 	tinymce.onAddEditor = new Dispatcher(tinymce);
12727 
12728 	tinymce.onRemoveEditor = new Dispatcher(tinymce);
12729 
12730 	tinymce.EditorManager = extend(tinymce, {
12731 		editors : [],
12732 
12733 		i18n : {},
12734 
12735 		activeEditor : null,
12736 
12737 		init : function(s) {
12738 			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
12739 
12740 			function createId(elm) {
12741 				var id = elm.id;
12742 	
12743 				// Use element id, or unique name or generate a unique id
12744 				if (!id) {
12745 					id = elm.name;
12746 	
12747 					if (id && !DOM.get(id)) {
12748 						id = elm.name;
12749 					} else {
12750 						// Generate unique name
12751 						id = DOM.uniqueId();
12752 					}
12753 
12754 					elm.setAttribute('id', id);
12755 				}
12756 
12757 				return id;
12758 			};
12759 
12760 			function execCallback(se, n, s) {
12761 				var f = se[n];
12762 
12763 				if (!f)
12764 					return;
12765 
12766 				if (tinymce.is(f, 'string')) {
12767 					s = f.replace(/\.\w+$/, '');
12768 					s = s ? tinymce.resolve(s) : 0;
12769 					f = tinymce.resolve(f);
12770 				}
12771 
12772 				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
12773 			};
12774 
12775 			function hasClass(n, c) {
12776 				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
12777 			};
12778 
12779 			t.settings = s;
12780 
12781 			// Legacy call
12782 			Event.bind(window, 'ready', function() {
12783 				var l, co;
12784 
12785 				execCallback(s, 'onpageload');
12786 
12787 				switch (s.mode) {
12788 					case "exact":
12789 						l = s.elements || '';
12790 
12791 						if(l.length > 0) {
12792 							each(explode(l), function(v) {
12793 								if (DOM.get(v)) {
12794 									ed = new tinymce.Editor(v, s);
12795 									el.push(ed);
12796 									ed.render(1);
12797 								} else {
12798 									each(document.forms, function(f) {
12799 										each(f.elements, function(e) {
12800 											if (e.name === v) {
12801 												v = 'mce_editor_' + instanceCounter++;
12802 												DOM.setAttrib(e, 'id', v);
12803 
12804 												ed = new tinymce.Editor(v, s);
12805 												el.push(ed);
12806 												ed.render(1);
12807 											}
12808 										});
12809 									});
12810 								}
12811 							});
12812 						}
12813 						break;
12814 
12815 					case "textareas":
12816 					case "specific_textareas":
12817 						each(DOM.select('textarea'), function(elm) {
12818 							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
12819 								return;
12820 
12821 							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
12822 								ed = new tinymce.Editor(createId(elm), s);
12823 								el.push(ed);
12824 								ed.render(1);
12825 							}
12826 						});
12827 						break;
12828 					
12829 					default:
12830 						if (s.types) {
12831 							// Process type specific selector
12832 							each(s.types, function(type) {
12833 								each(DOM.select(type.selector), function(elm) {
12834 									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
12835 									el.push(editor);
12836 									editor.render(1);
12837 								});
12838 							});
12839 						} else if (s.selector) {
12840 							// Process global selector
12841 							each(DOM.select(s.selector), function(elm) {
12842 								var editor = new tinymce.Editor(createId(elm), s);
12843 								el.push(editor);
12844 								editor.render(1);
12845 							});
12846 						}
12847 				}
12848 
12849 				// Call onInit when all editors are initialized
12850 				if (s.oninit) {
12851 					l = co = 0;
12852 
12853 					each(el, function(ed) {
12854 						co++;
12855 
12856 						if (!ed.initialized) {
12857 							// Wait for it
12858 							ed.onInit.add(function() {
12859 								l++;
12860 
12861 								// All done
12862 								if (l == co)
12863 									execCallback(s, 'oninit');
12864 							});
12865 						} else
12866 							l++;
12867 
12868 						// All done
12869 						if (l == co)
12870 							execCallback(s, 'oninit');					
12871 					});
12872 				}
12873 			});
12874 		},
12875 
12876 		get : function(id) {
12877 			if (id === undef)
12878 				return this.editors;
12879 
12880 			return this.editors[id];
12881 		},
12882 
12883 		getInstanceById : function(id) {
12884 			return this.get(id);
12885 		},
12886 
12887 		add : function(editor) {
12888 			var self = this, editors = self.editors;
12889 
12890 			// Add named and index editor instance
12891 			editors[editor.id] = editor;
12892 			editors.push(editor);
12893 
12894 			self._setActive(editor);
12895 			self.onAddEditor.dispatch(self, editor);
12896 
12897 
12898 			return editor;
12899 		},
12900 
12901 		remove : function(editor) {
12902 			var t = this, i, editors = t.editors;
12903 
12904 			// Not in the collection
12905 			if (!editors[editor.id])
12906 				return null;
12907 
12908 			delete editors[editor.id];
12909 
12910 			for (i = 0; i < editors.length; i++) {
12911 				if (editors[i] == editor) {
12912 					editors.splice(i, 1);
12913 					break;
12914 				}
12915 			}
12916 
12917 			// Select another editor since the active one was removed
12918 			if (t.activeEditor == editor)
12919 				t._setActive(editors[0]);
12920 
12921 			editor.destroy();
12922 			t.onRemoveEditor.dispatch(t, editor);
12923 
12924 			return editor;
12925 		},
12926 
12927 		execCommand : function(c, u, v) {
12928 			var t = this, ed = t.get(v), w;
12929 
12930 			function clr() {
12931 				ed.destroy();
12932 				w.detachEvent('onunload', clr);
12933 				w = w.tinyMCE = w.tinymce = null; // IE leak
12934 			};
12935 
12936 			// Manager commands
12937 			switch (c) {
12938 				case "mceFocus":
12939 					ed.focus();
12940 					return true;
12941 
12942 				case "mceAddEditor":
12943 				case "mceAddControl":
12944 					if (!t.get(v))
12945 						new tinymce.Editor(v, t.settings).render();
12946 
12947 					return true;
12948 
12949 				case "mceAddFrameControl":
12950 					w = v.window;
12951 
12952 					// Add tinyMCE global instance and tinymce namespace to specified window
12953 					w.tinyMCE = tinyMCE;
12954 					w.tinymce = tinymce;
12955 
12956 					tinymce.DOM.doc = w.document;
12957 					tinymce.DOM.win = w;
12958 
12959 					ed = new tinymce.Editor(v.element_id, v);
12960 					ed.render();
12961 
12962 					// Fix IE memory leaks
12963 					if (tinymce.isIE) {
12964 						w.attachEvent('onunload', clr);
12965 					}
12966 
12967 					v.page_window = null;
12968 
12969 					return true;
12970 
12971 				case "mceRemoveEditor":
12972 				case "mceRemoveControl":
12973 					if (ed)
12974 						ed.remove();
12975 
12976 					return true;
12977 
12978 				case 'mceToggleEditor':
12979 					if (!ed) {
12980 						t.execCommand('mceAddControl', 0, v);
12981 						return true;
12982 					}
12983 
12984 					if (ed.isHidden())
12985 						ed.show();
12986 					else
12987 						ed.hide();
12988 
12989 					return true;
12990 			}
12991 
12992 			// Run command on active editor
12993 			if (t.activeEditor)
12994 				return t.activeEditor.execCommand(c, u, v);
12995 
12996 			return false;
12997 		},
12998 
12999 		execInstanceCommand : function(id, c, u, v) {
13000 			var ed = this.get(id);
13001 
13002 			if (ed)
13003 				return ed.execCommand(c, u, v);
13004 
13005 			return false;
13006 		},
13007 
13008 		triggerSave : function() {
13009 			each(this.editors, function(e) {
13010 				e.save();
13011 			});
13012 		},
13013 
13014 		addI18n : function(p, o) {
13015 			var lo, i18n = this.i18n;
13016 
13017 			if (!tinymce.is(p, 'string')) {
13018 				each(p, function(o, lc) {
13019 					each(o, function(o, g) {
13020 						each(o, function(o, k) {
13021 							if (g === 'common')
13022 								i18n[lc + '.' + k] = o;
13023 							else
13024 								i18n[lc + '.' + g + '.' + k] = o;
13025 						});
13026 					});
13027 				});
13028 			} else {
13029 				each(o, function(o, k) {
13030 					i18n[p + '.' + k] = o;
13031 				});
13032 			}
13033 		},
13034 
13035 		// Private methods
13036 
13037 		_setActive : function(editor) {
13038 			this.selectedInstance = this.activeEditor = editor;
13039 		}
13040 	});
13041 })(tinymce);
13042 
13043 (function(tinymce) {
13044 	// Shorten these names
13045 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
13046 		each = tinymce.each, isGecko = tinymce.isGecko,
13047 		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
13048 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13049 		explode = tinymce.explode;
13050 
13051 	tinymce.create('tinymce.Editor', {
13052 		Editor : function(id, settings) {
13053 			var self = this, TRUE = true;
13054 
13055 			self.settings = settings = extend({
13056 				id : id,
13057 				language : 'en',
13058 				theme : 'advanced',
13059 				skin : 'default',
13060 				delta_width : 0,
13061 				delta_height : 0,
13062 				popup_css : '',
13063 				plugins : '',
13064 				document_base_url : tinymce.documentBaseURL,
13065 				add_form_submit_trigger : TRUE,
13066 				submit_patch : TRUE,
13067 				add_unload_trigger : TRUE,
13068 				convert_urls : TRUE,
13069 				relative_urls : TRUE,
13070 				remove_script_host : TRUE,
13071 				table_inline_editing : false,
13072 				object_resizing : TRUE,
13073 				accessibility_focus : TRUE,
13074 				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
13075 				visual : TRUE,
13076 				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
13077 				font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
13078 				apply_source_formatting : TRUE,
13079 				directionality : 'ltr',
13080 				forced_root_block : 'p',
13081 				hidden_input : TRUE,
13082 				padd_empty_editor : TRUE,
13083 				render_ui : TRUE,
13084 				indentation : '30px',
13085 				fix_table_elements : TRUE,
13086 				inline_styles : TRUE,
13087 				convert_fonts_to_spans : TRUE,
13088 				indent : 'simple',
13089 				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13090 				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13091 				validate : TRUE,
13092 				entity_encoding : 'named',
13093 				url_converter : self.convertURL,
13094 				url_converter_scope : self,
13095 				ie7_compat : TRUE
13096 			}, settings);
13097 
13098 			self.id = self.editorId = id;
13099 
13100 			self.isNotDirty = false;
13101 
13102 			self.plugins = {};
13103 
13104 			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
13105 				base_uri : tinyMCE.baseURI
13106 			});
13107 
13108 			self.baseURI = tinymce.baseURI;
13109 
13110 			self.contentCSS = [];
13111 
13112 			self.contentStyles = [];
13113 
13114 			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
13115 			self.setupEvents();
13116 
13117 			// Internal command handler objects
13118 			self.execCommands = {};
13119 			self.queryStateCommands = {};
13120 			self.queryValueCommands = {};
13121 
13122 			// Call setup
13123 			self.execCallback('setup', self);
13124 		},
13125 
13126 		render : function(nst) {
13127 			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
13128 
13129 			// Page is not loaded yet, wait for it
13130 			if (!Event.domLoaded) {
13131 				Event.add(window, 'ready', function() {
13132 					t.render();
13133 				});
13134 				return;
13135 			}
13136 
13137 			tinyMCE.settings = s;
13138 
13139 			// Element not found, then skip initialization
13140 			if (!t.getElement())
13141 				return;
13142 
13143 			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
13144 			// here since the browser says it has contentEditable support but there is no visible caret.
13145 			if (tinymce.isIDevice && !tinymce.isIOS5)
13146 				return;
13147 
13148 			// Add hidden input for non input elements inside form elements
13149 			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
13150 				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
13151 
13152 			// Hide target element early to prevent content flashing
13153 			if (!s.content_editable) {
13154 				t.orgVisibility = t.getElement().style.visibility;
13155 				t.getElement().style.visibility = 'hidden';
13156 			}
13157 
13158 			if (tinymce.WindowManager)
13159 				t.windowManager = new tinymce.WindowManager(t);
13160 
13161 			if (s.encoding == 'xml') {
13162 				t.onGetContent.add(function(ed, o) {
13163 					if (o.save)
13164 						o.content = DOM.encode(o.content);
13165 				});
13166 			}
13167 
13168 			if (s.add_form_submit_trigger) {
13169 				t.onSubmit.addToTop(function() {
13170 					if (t.initialized) {
13171 						t.save();
13172 						t.isNotDirty = 1;
13173 					}
13174 				});
13175 			}
13176 
13177 			if (s.add_unload_trigger) {
13178 				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
13179 					if (t.initialized && !t.destroyed && !t.isHidden())
13180 						t.save({format : 'raw', no_events : true});
13181 				});
13182 			}
13183 
13184 			tinymce.addUnload(t.destroy, t);
13185 
13186 			if (s.submit_patch) {
13187 				t.onBeforeRenderUI.add(function() {
13188 					var n = t.getElement().form;
13189 
13190 					if (!n)
13191 						return;
13192 
13193 					// Already patched
13194 					if (n._mceOldSubmit)
13195 						return;
13196 
13197 					// Check page uses id="submit" or name="submit" for it's submit button
13198 					if (!n.submit.nodeType && !n.submit.length) {
13199 						t.formElement = n;
13200 						n._mceOldSubmit = n.submit;
13201 						n.submit = function() {
13202 							// Save all instances
13203 							tinymce.triggerSave();
13204 							t.isNotDirty = 1;
13205 
13206 							return t.formElement._mceOldSubmit(t.formElement);
13207 						};
13208 					}
13209 
13210 					n = null;
13211 				});
13212 			}
13213 
13214 			// Load scripts
13215 			function loadScripts() {
13216 				if (s.language && s.language_load !== false)
13217 					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
13218 
13219 				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
13220 					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
13221 
13222 				each(explode(s.plugins), function(p) {
13223 					if (p &&!PluginManager.urls[p]) {
13224 						if (p.charAt(0) == '-') {
13225 							p = p.substr(1, p.length);
13226 							var dependencies = PluginManager.dependencies(p);
13227 							each(dependencies, function(dep) {
13228 								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
13229 								dep = PluginManager.createUrl(defaultSettings, dep);
13230 								PluginManager.load(dep.resource, dep);
13231 							});
13232 						} else {
13233 							// Skip safari plugin, since it is removed as of 3.3b1
13234 							if (p == 'safari') {
13235 								return;
13236 							}
13237 							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
13238 						}
13239 					}
13240 				});
13241 
13242 				// Init when que is loaded
13243 				sl.loadQueue(function() {
13244 					if (!t.removed)
13245 						t.init();
13246 				});
13247 			};
13248 
13249 			loadScripts();
13250 		},
13251 
13252 		init : function() {
13253 			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
13254 
13255 			tinymce.add(t);
13256 
13257 			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
13258 
13259 			if (s.theme) {
13260 				if (typeof s.theme != "function") {
13261 					s.theme = s.theme.replace(/-/, '');
13262 					o = ThemeManager.get(s.theme);
13263 					t.theme = new o();
13264 
13265 					if (t.theme.init)
13266 						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
13267 				} else {
13268 					t.theme = s.theme;
13269 				}
13270 			}
13271 
13272 			function initPlugin(p) {
13273 				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
13274 				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
13275 					each(PluginManager.dependencies(p), function(dep){
13276 						initPlugin(dep);
13277 					});
13278 					po = new c(t, u);
13279 
13280 					t.plugins[p] = po;
13281 
13282 					if (po.init) {
13283 						po.init(t, u);
13284 						initializedPlugins.push(p);
13285 					}
13286 				}
13287 			}
13288 			
13289 			// Create all plugins
13290 			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
13291 
13292 			// Setup popup CSS path(s)
13293 			if (s.popup_css !== false) {
13294 				if (s.popup_css)
13295 					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
13296 				else
13297 					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
13298 			}
13299 
13300 			if (s.popup_css_add)
13301 				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
13302 
13303 			t.controlManager = new tinymce.ControlManager(t);
13304 
13305 			// Enables users to override the control factory
13306 			t.onBeforeRenderUI.dispatch(t, t.controlManager);
13307 
13308 			// Measure box
13309 			if (s.render_ui && t.theme) {
13310 				t.orgDisplay = e.style.display;
13311 
13312 				if (typeof s.theme != "function") {
13313 					w = s.width || e.style.width || e.offsetWidth;
13314 					h = s.height || e.style.height || e.offsetHeight;
13315 					mh = s.min_height || 100;
13316 					re = /^[0-9\.]+(|px)$/i;
13317 
13318 					if (re.test('' + w))
13319 						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
13320 
13321 					if (re.test('' + h))
13322 						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
13323 
13324 					// Render UI
13325 					o = t.theme.renderUI({
13326 						targetNode : e,
13327 						width : w,
13328 						height : h,
13329 						deltaWidth : s.delta_width,
13330 						deltaHeight : s.delta_height
13331 					});
13332 
13333 					// Resize editor
13334 					DOM.setStyles(o.sizeContainer || o.editorContainer, {
13335 						width : w,
13336 						height : h
13337 					});
13338 
13339 					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
13340 					if (h < mh)
13341 						h = mh;
13342 				} else {
13343 					o = s.theme(t, e);
13344 
13345 					// Convert element type to id:s
13346 					if (o.editorContainer.nodeType) {
13347 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
13348 					}
13349 
13350 					// Convert element type to id:s
13351 					if (o.iframeContainer.nodeType) {
13352 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
13353 					}
13354 
13355 					// Use specified iframe height or the targets offsetHeight
13356 					h = o.iframeHeight || e.offsetHeight;
13357 
13358 					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
13359 					if (isIE) {
13360 						t.onInit.add(function(ed) {
13361 							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
13362 								ed.lastIERng = ed.selection.getRng();
13363 							});
13364 						});
13365 					}
13366 				}
13367 
13368 				t.editorContainer = o.editorContainer;
13369 			}
13370 
13371 			// Load specified content CSS last
13372 			if (s.content_css) {
13373 				each(explode(s.content_css), function(u) {
13374 					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
13375 				});
13376 			}
13377 
13378 			// Content editable mode ends here
13379 			if (s.content_editable) {
13380 				e = n = o = null; // Fix IE leak
13381 				return t.initContentBody();
13382 			}
13383 
13384 			// User specified a document.domain value
13385 			if (document.domain && location.hostname != document.domain)
13386 				tinymce.relaxedDomain = document.domain;
13387 
13388 			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
13389 
13390 			// We only need to override paths if we have to
13391 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
13392 			if (s.document_base_url != tinymce.documentBaseURL)
13393 				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
13394 
13395 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
13396 			if (s.ie7_compat)
13397 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
13398 			else
13399 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
13400 
13401 			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
13402 
13403 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
13404 			for (i = 0; i < t.contentCSS.length; i++) {
13405 				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
13406 			}
13407 
13408 			t.contentCSS = [];
13409 
13410 			bi = s.body_id || 'tinymce';
13411 			if (bi.indexOf('=') != -1) {
13412 				bi = t.getParam('body_id', '', 'hash');
13413 				bi = bi[t.id] || bi;
13414 			}
13415 
13416 			bc = s.body_class || '';
13417 			if (bc.indexOf('=') != -1) {
13418 				bc = t.getParam('body_class', '', 'hash');
13419 				bc = bc[t.id] || '';
13420 			}
13421 
13422 			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
13423 
13424 			// Domain relaxing enabled, then set document domain
13425 			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
13426 				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
13427 				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
13428 			}
13429 
13430 			// Create iframe
13431 			// TODO: ACC add the appropriate description on this.
13432 			n = DOM.add(o.iframeContainer, 'iframe', { 
13433 				id : t.id + "_ifr",
13434 				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
13435 				frameBorder : '0',
13436 				allowTransparency : "true",
13437 				title : s.aria_label,
13438 				style : {
13439 					width : '100%',
13440 					height : h,
13441 					display : 'block' // Important for Gecko to render the iframe correctly
13442 				}
13443 			});
13444 
13445 			t.contentAreaContainer = o.iframeContainer;
13446 
13447 			if (o.editorContainer) {
13448 				DOM.get(o.editorContainer).style.display = t.orgDisplay;
13449 			}
13450 
13451 			// Restore visibility on target element
13452 			e.style.visibility = t.orgVisibility;
13453 
13454 			DOM.get(t.id).style.display = 'none';
13455 			DOM.setAttrib(t.id, 'aria-hidden', true);
13456 
13457 			if (!tinymce.relaxedDomain || !u)
13458 				t.initContentBody();
13459 
13460 			e = n = o = null; // Cleanup
13461 		},
13462 
13463 		initContentBody : function() {
13464 			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
13465 
13466 			// Setup iframe body
13467 			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
13468 				doc.open();
13469 				doc.write(self.iframeHTML);
13470 				doc.close();
13471 
13472 				if (tinymce.relaxedDomain)
13473 					doc.domain = tinymce.relaxedDomain;
13474 			}
13475 
13476 			if (settings.content_editable) {
13477 				DOM.addClass(targetElm, 'mceContentBody');
13478 				self.contentDocument = doc = settings.content_document || document;
13479 				self.contentWindow = settings.content_window || window;
13480 				self.bodyElement = targetElm;
13481 
13482 				// Prevent leak in IE
13483 				settings.content_document = settings.content_window = null;
13484 			}
13485 
13486 			// It will not steal focus while setting contentEditable
13487 			body = self.getBody();
13488 			body.disabled = true;
13489 
13490 			if (!settings.readonly)
13491 				body.contentEditable = self.getParam('content_editable_state', true);
13492 
13493 			body.disabled = false;
13494 
13495 			self.schema = new tinymce.html.Schema(settings);
13496 
13497 			self.dom = new tinymce.dom.DOMUtils(doc, {
13498 				keep_values : true,
13499 				url_converter : self.convertURL,
13500 				url_converter_scope : self,
13501 				hex_colors : settings.force_hex_style_colors,
13502 				class_filter : settings.class_filter,
13503 				update_styles : true,
13504 				root_element : settings.content_editable ? self.id : null,
13505 				schema : self.schema
13506 			});
13507 
13508 			self.parser = new tinymce.html.DomParser(settings, self.schema);
13509 
13510 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
13511 			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
13512 				var i = nodes.length, node, dom = self.dom, value, internalName;
13513 
13514 				while (i--) {
13515 					node = nodes[i];
13516 					value = node.attr(name);
13517 					internalName = 'data-mce-' + name;
13518 
13519 					// Add internal attribute if we need to we don't on a refresh of the document
13520 					if (!node.attributes.map[internalName]) {	
13521 						if (name === "style")
13522 							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
13523 						else
13524 							node.attr(internalName, self.convertURL(value, name, node.name));
13525 					}
13526 				}
13527 			});
13528 
13529 			// Keep scripts from executing
13530 			self.parser.addNodeFilter('script', function(nodes, name) {
13531 				var i = nodes.length, node;
13532 
13533 				while (i--) {
13534 					node = nodes[i];
13535 					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
13536 				}
13537 			});
13538 
13539 			self.parser.addNodeFilter('#cdata', function(nodes, name) {
13540 				var i = nodes.length, node;
13541 
13542 				while (i--) {
13543 					node = nodes[i];
13544 					node.type = 8;
13545 					node.name = '#comment';
13546 					node.value = '[CDATA[' + node.value + ']]';
13547 				}
13548 			});
13549 
13550 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
13551 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
13552 
13553 				while (i--) {
13554 					node = nodes[i];
13555 
13556 					if (node.isEmpty(nonEmptyElements))
13557 						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
13558 				}
13559 			});
13560 
13561 			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
13562 
13563 			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
13564 
13565 			self.formatter = new tinymce.Formatter(self);
13566 
13567 			self.undoManager = new tinymce.UndoManager(self);
13568 
13569 			self.forceBlocks = new tinymce.ForceBlocks(self);
13570 			self.enterKey = new tinymce.EnterKey(self);
13571 			self.editorCommands = new tinymce.EditorCommands(self);
13572 
13573 			self.onExecCommand.add(function(editor, command) {
13574 				// Don't refresh the select lists until caret move
13575 				if (!/^(FontName|FontSize)$/.test(command))
13576 					self.nodeChanged();
13577 			});
13578 
13579 			// Pass through
13580 			self.serializer.onPreProcess.add(function(se, o) {
13581 				return self.onPreProcess.dispatch(self, o, se);
13582 			});
13583 
13584 			self.serializer.onPostProcess.add(function(se, o) {
13585 				return self.onPostProcess.dispatch(self, o, se);
13586 			});
13587 
13588 			self.onPreInit.dispatch(self);
13589 
13590 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
13591 				doc.body.spellcheck = false;
13592 
13593 			if (!settings.readonly) {
13594 				self.bindNativeEvents();
13595 			}
13596 
13597 			self.controlManager.onPostRender.dispatch(self, self.controlManager);
13598 			self.onPostRender.dispatch(self);
13599 
13600 			self.quirks = tinymce.util.Quirks(self);
13601 
13602 			if (settings.directionality)
13603 				body.dir = settings.directionality;
13604 
13605 			if (settings.nowrap)
13606 				body.style.whiteSpace = "nowrap";
13607 
13608 			if (settings.protect) {
13609 				self.onBeforeSetContent.add(function(ed, o) {
13610 					each(settings.protect, function(pattern) {
13611 						o.content = o.content.replace(pattern, function(str) {
13612 							return '<!--mce:protected ' + escape(str) + '-->';
13613 						});
13614 					});
13615 				});
13616 			}
13617 
13618 			// Add visual aids when new contents is added
13619 			self.onSetContent.add(function() {
13620 				self.addVisual(self.getBody());
13621 			});
13622 
13623 			// Remove empty contents
13624 			if (settings.padd_empty_editor) {
13625 				self.onPostProcess.add(function(ed, o) {
13626 					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
13627 				});
13628 			}
13629 
13630 			self.load({initial : true, format : 'html'});
13631 			self.startContent = self.getContent({format : 'raw'});
13632 
13633 			self.initialized = true;
13634 
13635 			self.onInit.dispatch(self);
13636 			self.execCallback('setupcontent_callback', self.id, body, doc);
13637 			self.execCallback('init_instance_callback', self);
13638 			self.focus(true);
13639 			self.nodeChanged({initial : true});
13640 
13641 			// Add editor specific CSS styles
13642 			if (self.contentStyles.length > 0) {
13643 				contentCssText = '';
13644 
13645 				each(self.contentStyles, function(style) {
13646 					contentCssText += style + "\r\n";
13647 				});
13648 
13649 				self.dom.addStyle(contentCssText);
13650 			}
13651 
13652 			// Load specified content CSS last
13653 			each(self.contentCSS, function(url) {
13654 				self.dom.loadCSS(url);
13655 			});
13656 
13657 			// Handle auto focus
13658 			if (settings.auto_focus) {
13659 				setTimeout(function () {
13660 					var ed = tinymce.get(settings.auto_focus);
13661 
13662 					ed.selection.select(ed.getBody(), 1);
13663 					ed.selection.collapse(1);
13664 					ed.getBody().focus();
13665 					ed.getWin().focus();
13666 				}, 100);
13667 			}
13668 
13669 			// Clean up references for IE
13670 			targetElm = doc = body = null;
13671 		},
13672 
13673 		focus : function(skip_focus) {
13674 			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
13675 
13676 			if (!skip_focus) {
13677 				if (self.lastIERng) {
13678 					selection.setRng(self.lastIERng);
13679 				}
13680 
13681 				// Get selected control element
13682 				ieRng = selection.getRng();
13683 				if (ieRng.item) {
13684 					controlElm = ieRng.item(0);
13685 				}
13686 
13687 				self._refreshContentEditable();
13688 
13689 				// Focus the window iframe
13690 				if (!contentEditable) {
13691 					self.getWin().focus();
13692 				}
13693 
13694 				// Focus the body as well since it's contentEditable
13695 				if (tinymce.isGecko || contentEditable) {
13696 					body = self.getBody();
13697 
13698 					// Check for setActive since it doesn't scroll to the element
13699 					if (body.setActive) {
13700 						body.setActive();
13701 					} else {
13702 						body.focus();
13703 					}
13704 
13705 					if (contentEditable) {
13706 						selection.normalize();
13707 					}
13708 				}
13709 
13710 				// Restore selected control element
13711 				// This is needed when for example an image is selected within a
13712 				// layer a call to focus will then remove the control selection
13713 				if (controlElm && controlElm.ownerDocument == doc) {
13714 					ieRng = doc.body.createControlRange();
13715 					ieRng.addElement(controlElm);
13716 					ieRng.select();
13717 				}
13718 			}
13719 
13720 			if (tinymce.activeEditor != self) {
13721 				if ((oed = tinymce.activeEditor) != null)
13722 					oed.onDeactivate.dispatch(oed, self);
13723 
13724 				self.onActivate.dispatch(self, oed);
13725 			}
13726 
13727 			tinymce._setActive(self);
13728 		},
13729 
13730 		execCallback : function(n) {
13731 			var t = this, f = t.settings[n], s;
13732 
13733 			if (!f)
13734 				return;
13735 
13736 			// Look through lookup
13737 			if (t.callbackLookup && (s = t.callbackLookup[n])) {
13738 				f = s.func;
13739 				s = s.scope;
13740 			}
13741 
13742 			if (is(f, 'string')) {
13743 				s = f.replace(/\.\w+$/, '');
13744 				s = s ? tinymce.resolve(s) : 0;
13745 				f = tinymce.resolve(f);
13746 				t.callbackLookup = t.callbackLookup || {};
13747 				t.callbackLookup[n] = {func : f, scope : s};
13748 			}
13749 
13750 			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
13751 		},
13752 
13753 		translate : function(s) {
13754 			var c = this.settings.language || 'en', i18n = tinymce.i18n;
13755 
13756 			if (!s)
13757 				return '';
13758 
13759 			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
13760 				return i18n[c + '.' + b] || '{#' + b + '}';
13761 			});
13762 		},
13763 
13764 		getLang : function(n, dv) {
13765 			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
13766 		},
13767 
13768 		getParam : function(n, dv, ty) {
13769 			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
13770 
13771 			if (ty === 'hash') {
13772 				o = {};
13773 
13774 				if (is(v, 'string')) {
13775 					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
13776 						v = v.split('=');
13777 
13778 						if (v.length > 1)
13779 							o[tr(v[0])] = tr(v[1]);
13780 						else
13781 							o[tr(v[0])] = tr(v);
13782 					});
13783 				} else
13784 					o = v;
13785 
13786 				return o;
13787 			}
13788 
13789 			return v;
13790 		},
13791 
13792 		nodeChanged : function(o) {
13793 			var self = this, selection = self.selection, node;
13794 
13795 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
13796 			if (self.initialized) {
13797 				o = o || {};
13798 
13799 				// Get start node
13800 				node = selection.getStart() || self.getBody();
13801 				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
13802 
13803 				// Get parents and add them to object
13804 				o.parents = [];
13805 				self.dom.getParent(node, function(node) {
13806 					if (node.nodeName == 'BODY')
13807 						return true;
13808 
13809 					o.parents.push(node);
13810 				});
13811 
13812 				self.onNodeChange.dispatch(
13813 					self,
13814 					o ? o.controlManager || self.controlManager : self.controlManager,
13815 					node,
13816 					selection.isCollapsed(),
13817 					o
13818 				);
13819 			}
13820 		},
13821 
13822 		addButton : function(name, settings) {
13823 			var self = this;
13824 
13825 			self.buttons = self.buttons || {};
13826 			self.buttons[name] = settings;
13827 		},
13828 
13829 		addCommand : function(name, callback, scope) {
13830 			this.execCommands[name] = {func : callback, scope : scope || this};
13831 		},
13832 
13833 		addQueryStateHandler : function(name, callback, scope) {
13834 			this.queryStateCommands[name] = {func : callback, scope : scope || this};
13835 		},
13836 
13837 		addQueryValueHandler : function(name, callback, scope) {
13838 			this.queryValueCommands[name] = {func : callback, scope : scope || this};
13839 		},
13840 
13841 		addShortcut : function(pa, desc, cmd_func, sc) {
13842 			var t = this, c;
13843 
13844 			if (t.settings.custom_shortcuts === false)
13845 				return false;
13846 
13847 			t.shortcuts = t.shortcuts || {};
13848 
13849 			if (is(cmd_func, 'string')) {
13850 				c = cmd_func;
13851 
13852 				cmd_func = function() {
13853 					t.execCommand(c, false, null);
13854 				};
13855 			}
13856 
13857 			if (is(cmd_func, 'object')) {
13858 				c = cmd_func;
13859 
13860 				cmd_func = function() {
13861 					t.execCommand(c[0], c[1], c[2]);
13862 				};
13863 			}
13864 
13865 			each(explode(pa), function(pa) {
13866 				var o = {
13867 					func : cmd_func,
13868 					scope : sc || this,
13869 					desc : t.translate(desc),
13870 					alt : false,
13871 					ctrl : false,
13872 					shift : false
13873 				};
13874 
13875 				each(explode(pa, '+'), function(v) {
13876 					switch (v) {
13877 						case 'alt':
13878 						case 'ctrl':
13879 						case 'shift':
13880 							o[v] = true;
13881 							break;
13882 
13883 						default:
13884 							o.charCode = v.charCodeAt(0);
13885 							o.keyCode = v.toUpperCase().charCodeAt(0);
13886 					}
13887 				});
13888 
13889 				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
13890 			});
13891 
13892 			return true;
13893 		},
13894 
13895 		execCommand : function(cmd, ui, val, a) {
13896 			var t = this, s = 0, o, st;
13897 
13898 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
13899 				t.focus();
13900 
13901 			a = extend({}, a);
13902 			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
13903 			if (a.terminate)
13904 				return false;
13905 
13906 			// Command callback
13907 			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
13908 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13909 				return true;
13910 			}
13911 
13912 			// Registred commands
13913 			if (o = t.execCommands[cmd]) {
13914 				st = o.func.call(o.scope, ui, val);
13915 
13916 				// Fall through on true
13917 				if (st !== true) {
13918 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13919 					return st;
13920 				}
13921 			}
13922 
13923 			// Plugin commands
13924 			each(t.plugins, function(p) {
13925 				if (p.execCommand && p.execCommand(cmd, ui, val)) {
13926 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13927 					s = 1;
13928 					return false;
13929 				}
13930 			});
13931 
13932 			if (s)
13933 				return true;
13934 
13935 			// Theme commands
13936 			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
13937 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13938 				return true;
13939 			}
13940 
13941 			// Editor commands
13942 			if (t.editorCommands.execCommand(cmd, ui, val)) {
13943 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13944 				return true;
13945 			}
13946 
13947 			// Browser commands
13948 			t.getDoc().execCommand(cmd, ui, val);
13949 			t.onExecCommand.dispatch(t, cmd, ui, val, a);
13950 		},
13951 
13952 		queryCommandState : function(cmd) {
13953 			var t = this, o, s;
13954 
13955 			// Is hidden then return undefined
13956 			if (t._isHidden())
13957 				return;
13958 
13959 			// Registred commands
13960 			if (o = t.queryStateCommands[cmd]) {
13961 				s = o.func.call(o.scope);
13962 
13963 				// Fall though on true
13964 				if (s !== true)
13965 					return s;
13966 			}
13967 
13968 			// Registred commands
13969 			o = t.editorCommands.queryCommandState(cmd);
13970 			if (o !== -1)
13971 				return o;
13972 
13973 			// Browser commands
13974 			try {
13975 				return this.getDoc().queryCommandState(cmd);
13976 			} catch (ex) {
13977 				// Fails sometimes see bug: 1896577
13978 			}
13979 		},
13980 
13981 		queryCommandValue : function(c) {
13982 			var t = this, o, s;
13983 
13984 			// Is hidden then return undefined
13985 			if (t._isHidden())
13986 				return;
13987 
13988 			// Registred commands
13989 			if (o = t.queryValueCommands[c]) {
13990 				s = o.func.call(o.scope);
13991 
13992 				// Fall though on true
13993 				if (s !== true)
13994 					return s;
13995 			}
13996 
13997 			// Registred commands
13998 			o = t.editorCommands.queryCommandValue(c);
13999 			if (is(o))
14000 				return o;
14001 
14002 			// Browser commands
14003 			try {
14004 				return this.getDoc().queryCommandValue(c);
14005 			} catch (ex) {
14006 				// Fails sometimes see bug: 1896577
14007 			}
14008 		},
14009 
14010 		show : function() {
14011 			var self = this;
14012 
14013 			DOM.show(self.getContainer());
14014 			DOM.hide(self.id);
14015 			self.load();
14016 		},
14017 
14018 		hide : function() {
14019 			var self = this, doc = self.getDoc();
14020 
14021 			// Fixed bug where IE has a blinking cursor left from the editor
14022 			if (isIE && doc)
14023 				doc.execCommand('SelectAll');
14024 
14025 			// We must save before we hide so Safari doesn't crash
14026 			self.save();
14027 			DOM.hide(self.getContainer());
14028 			DOM.setStyle(self.id, 'display', self.orgDisplay);
14029 		},
14030 
14031 		isHidden : function() {
14032 			return !DOM.isHidden(this.id);
14033 		},
14034 
14035 		setProgressState : function(b, ti, o) {
14036 			this.onSetProgressState.dispatch(this, b, ti, o);
14037 
14038 			return b;
14039 		},
14040 
14041 		load : function(o) {
14042 			var t = this, e = t.getElement(), h;
14043 
14044 			if (e) {
14045 				o = o || {};
14046 				o.load = true;
14047 
14048 				// Double encode existing entities in the value
14049 				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
14050 				o.element = e;
14051 
14052 				if (!o.no_events)
14053 					t.onLoadContent.dispatch(t, o);
14054 
14055 				o.element = e = null;
14056 
14057 				return h;
14058 			}
14059 		},
14060 
14061 		save : function(o) {
14062 			var t = this, e = t.getElement(), h, f;
14063 
14064 			if (!e || !t.initialized)
14065 				return;
14066 
14067 			o = o || {};
14068 			o.save = true;
14069 
14070 			o.element = e;
14071 			h = o.content = t.getContent(o);
14072 
14073 			if (!o.no_events)
14074 				t.onSaveContent.dispatch(t, o);
14075 
14076 			h = o.content;
14077 
14078 			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
14079 				e.innerHTML = h;
14080 
14081 				// Update hidden form element
14082 				if (f = DOM.getParent(t.id, 'form')) {
14083 					each(f.elements, function(e) {
14084 						if (e.name == t.id) {
14085 							e.value = h;
14086 							return false;
14087 						}
14088 					});
14089 				}
14090 			} else
14091 				e.value = h;
14092 
14093 			o.element = e = null;
14094 
14095 			return h;
14096 		},
14097 
14098 		setContent : function(content, args) {
14099 			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
14100 
14101 			// Setup args object
14102 			args = args || {};
14103 			args.format = args.format || 'html';
14104 			args.set = true;
14105 			args.content = content;
14106 
14107 			// Do preprocessing
14108 			if (!args.no_events)
14109 				self.onBeforeSetContent.dispatch(self, args);
14110 
14111 			content = args.content;
14112 
14113 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
14114 			// It will also be impossible to place the caret in the editor unless there is a BR element present
14115 			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
14116 				forcedRootBlockName = self.settings.forced_root_block;
14117 				if (forcedRootBlockName)
14118 					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
14119 				else
14120 					content = '<br data-mce-bogus="1">';
14121 
14122 				body.innerHTML = content;
14123 				self.selection.select(body, true);
14124 				self.selection.collapse(true);
14125 				return;
14126 			}
14127 
14128 			// Parse and serialize the html
14129 			if (args.format !== 'raw') {
14130 				content = new tinymce.html.Serializer({}, self.schema).serialize(
14131 					self.parser.parse(content)
14132 				);
14133 			}
14134 
14135 			// Set the new cleaned contents to the editor
14136 			args.content = tinymce.trim(content);
14137 			self.dom.setHTML(body, args.content);
14138 
14139 			// Do post processing
14140 			if (!args.no_events)
14141 				self.onSetContent.dispatch(self, args);
14142 
14143 			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
14144 			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
14145 				self.selection.normalize();
14146 			}
14147 
14148 			return args.content;
14149 		},
14150 
14151 		getContent : function(args) {
14152 			var self = this, content;
14153 
14154 			// Setup args object
14155 			args = args || {};
14156 			args.format = args.format || 'html';
14157 			args.get = true;
14158 			args.getInner = true;
14159 
14160 			// Do preprocessing
14161 			if (!args.no_events)
14162 				self.onBeforeGetContent.dispatch(self, args);
14163 
14164 			// Get raw contents or by default the cleaned contents
14165 			if (args.format == 'raw')
14166 				content = self.getBody().innerHTML;
14167 			else
14168 				content = self.serializer.serialize(self.getBody(), args);
14169 
14170 			args.content = tinymce.trim(content);
14171 
14172 			// Do post processing
14173 			if (!args.no_events)
14174 				self.onGetContent.dispatch(self, args);
14175 
14176 			return args.content;
14177 		},
14178 
14179 		isDirty : function() {
14180 			var self = this;
14181 
14182 			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
14183 		},
14184 
14185 		getContainer : function() {
14186 			var self = this;
14187 
14188 			if (!self.container)
14189 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
14190 
14191 			return self.container;
14192 		},
14193 
14194 		getContentAreaContainer : function() {
14195 			return this.contentAreaContainer;
14196 		},
14197 
14198 		getElement : function() {
14199 			return DOM.get(this.settings.content_element || this.id);
14200 		},
14201 
14202 		getWin : function() {
14203 			var self = this, elm;
14204 
14205 			if (!self.contentWindow) {
14206 				elm = DOM.get(self.id + "_ifr");
14207 
14208 				if (elm)
14209 					self.contentWindow = elm.contentWindow;
14210 			}
14211 
14212 			return self.contentWindow;
14213 		},
14214 
14215 		getDoc : function() {
14216 			var self = this, win;
14217 
14218 			if (!self.contentDocument) {
14219 				win = self.getWin();
14220 
14221 				if (win)
14222 					self.contentDocument = win.document;
14223 			}
14224 
14225 			return self.contentDocument;
14226 		},
14227 
14228 		getBody : function() {
14229 			return this.bodyElement || this.getDoc().body;
14230 		},
14231 
14232 		convertURL : function(url, name, elm) {
14233 			var self = this, settings = self.settings;
14234 
14235 			// Use callback instead
14236 			if (settings.urlconverter_callback)
14237 				return self.execCallback('urlconverter_callback', url, elm, true, name);
14238 
14239 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
14240 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
14241 				return url;
14242 
14243 			// Convert to relative
14244 			if (settings.relative_urls)
14245 				return self.documentBaseURI.toRelative(url);
14246 
14247 			// Convert to absolute
14248 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
14249 
14250 			return url;
14251 		},
14252 
14253 		addVisual : function(elm) {
14254 			var self = this, settings = self.settings, dom = self.dom, cls;
14255 
14256 			elm = elm || self.getBody();
14257 
14258 			if (!is(self.hasVisual))
14259 				self.hasVisual = settings.visual;
14260 
14261 			each(dom.select('table,a', elm), function(elm) {
14262 				var value;
14263 
14264 				switch (elm.nodeName) {
14265 					case 'TABLE':
14266 						cls = settings.visual_table_class || 'mceItemTable';
14267 						value = dom.getAttrib(elm, 'border');
14268 
14269 						if (!value || value == '0') {
14270 							if (self.hasVisual)
14271 								dom.addClass(elm, cls);
14272 							else
14273 								dom.removeClass(elm, cls);
14274 						}
14275 
14276 						return;
14277 
14278 					case 'A':
14279 						if (!dom.getAttrib(elm, 'href', false)) {
14280 							value = dom.getAttrib(elm, 'name') || elm.id;
14281 							cls = 'mceItemAnchor';
14282 
14283 							if (value) {
14284 								if (self.hasVisual)
14285 									dom.addClass(elm, cls);
14286 								else
14287 									dom.removeClass(elm, cls);
14288 							}
14289 						}
14290 
14291 						return;
14292 				}
14293 			});
14294 
14295 			self.onVisualAid.dispatch(self, elm, self.hasVisual);
14296 		},
14297 
14298 		remove : function() {
14299 			var self = this, elm = self.getContainer();
14300 
14301 			if (!self.removed) {
14302 				self.removed = 1; // Cancels post remove event execution
14303 				self.hide();
14304 
14305 				// Don't clear the window or document if content editable
14306 				// is enabled since other instances might still be present
14307 				if (!self.settings.content_editable) {
14308 					Event.unbind(self.getWin());
14309 					Event.unbind(self.getDoc());
14310 				}
14311 
14312 				Event.unbind(self.getBody());
14313 				Event.clear(elm);
14314 
14315 				self.execCallback('remove_instance_callback', self);
14316 				self.onRemove.dispatch(self);
14317 
14318 				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
14319 				self.onExecCommand.listeners = [];
14320 
14321 				tinymce.remove(self);
14322 				DOM.remove(elm);
14323 			}
14324 		},
14325 
14326 		destroy : function(s) {
14327 			var t = this;
14328 
14329 			// One time is enough
14330 			if (t.destroyed)
14331 				return;
14332 
14333 			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
14334 			if (isGecko) {
14335 				Event.unbind(t.getDoc());
14336 				Event.unbind(t.getWin());
14337 				Event.unbind(t.getBody());
14338 			}
14339 
14340 			if (!s) {
14341 				tinymce.removeUnload(t.destroy);
14342 				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
14343 
14344 				// Manual destroy
14345 				if (t.theme && t.theme.destroy)
14346 					t.theme.destroy();
14347 
14348 				// Destroy controls, selection and dom
14349 				t.controlManager.destroy();
14350 				t.selection.destroy();
14351 				t.dom.destroy();
14352 			}
14353 
14354 			if (t.formElement) {
14355 				t.formElement.submit = t.formElement._mceOldSubmit;
14356 				t.formElement._mceOldSubmit = null;
14357 			}
14358 
14359 			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
14360 
14361 			if (t.selection)
14362 				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
14363 
14364 			t.destroyed = 1;
14365 		},
14366 
14367 		// Internal functions
14368 
14369 		_refreshContentEditable : function() {
14370 			var self = this, body, parent;
14371 
14372 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
14373 			if (self._isHidden()) {
14374 				body = self.getBody();
14375 				parent = body.parentNode;
14376 
14377 				parent.removeChild(body);
14378 				parent.appendChild(body);
14379 
14380 				body.focus();
14381 			}
14382 		},
14383 
14384 		_isHidden : function() {
14385 			var s;
14386 
14387 			if (!isGecko)
14388 				return 0;
14389 
14390 			// Weird, wheres that cursor selection?
14391 			s = this.selection.getSel();
14392 			return (!s || !s.rangeCount || s.rangeCount === 0);
14393 		}
14394 	});
14395 })(tinymce);
14396 (function(tinymce) {
14397 	var each = tinymce.each;
14398 
14399 	tinymce.Editor.prototype.setupEvents = function() {
14400 		var self = this, settings = self.settings;
14401 
14402 		// Add events to the editor
14403 		each([
14404 			'onPreInit',
14405 
14406 			'onBeforeRenderUI',
14407 
14408 			'onPostRender',
14409 
14410 			'onLoad',
14411 
14412 			'onInit',
14413 
14414 			'onRemove',
14415 
14416 			'onActivate',
14417 
14418 			'onDeactivate',
14419 
14420 			'onClick',
14421 
14422 			'onEvent',
14423 
14424 			'onMouseUp',
14425 
14426 			'onMouseDown',
14427 
14428 			'onDblClick',
14429 
14430 			'onKeyDown',
14431 
14432 			'onKeyUp',
14433 
14434 			'onKeyPress',
14435 
14436 			'onContextMenu',
14437 
14438 			'onSubmit',
14439 
14440 			'onReset',
14441 
14442 			'onPaste',
14443 
14444 			'onPreProcess',
14445 
14446 			'onPostProcess',
14447 
14448 			'onBeforeSetContent',
14449 
14450 			'onBeforeGetContent',
14451 
14452 			'onSetContent',
14453 
14454 			'onGetContent',
14455 
14456 			'onLoadContent',
14457 
14458 			'onSaveContent',
14459 
14460 			'onNodeChange',
14461 
14462 			'onChange',
14463 
14464 			'onBeforeExecCommand',
14465 
14466 			'onExecCommand',
14467 
14468 			'onUndo',
14469 
14470 			'onRedo',
14471 
14472 			'onVisualAid',
14473 
14474 			'onSetProgressState',
14475 
14476 			'onSetAttrib'
14477 		], function(name) {
14478 			self[name] = new tinymce.util.Dispatcher(self);
14479 		});
14480 
14481 		// Handle legacy cleanup_callback option
14482 		if (settings.cleanup_callback) {
14483 			self.onBeforeSetContent.add(function(ed, o) {
14484 				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14485 			});
14486 
14487 			self.onPreProcess.add(function(ed, o) {
14488 				if (o.set)
14489 					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
14490 
14491 				if (o.get)
14492 					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
14493 			});
14494 
14495 			self.onPostProcess.add(function(ed, o) {
14496 				if (o.set)
14497 					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14498 
14499 				if (o.get)						
14500 					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
14501 			});
14502 		}
14503 
14504 		// Handle legacy save_callback option
14505 		if (settings.save_callback) {
14506 			self.onGetContent.add(function(ed, o) {
14507 				if (o.save)
14508 					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14509 			});
14510 		}
14511 
14512 		// Handle legacy handle_event_callback option
14513 		if (settings.handle_event_callback) {
14514 			self.onEvent.add(function(ed, e, o) {
14515 				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
14516 					e.preventDefault();
14517 					e.stopPropagation();
14518 				}
14519 			});
14520 		}
14521 
14522 		// Handle legacy handle_node_change_callback option
14523 		if (settings.handle_node_change_callback) {
14524 			self.onNodeChange.add(function(ed, cm, n) {
14525 				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
14526 			});
14527 		}
14528 
14529 		// Handle legacy save_callback option
14530 		if (settings.save_callback) {
14531 			self.onSaveContent.add(function(ed, o) {
14532 				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14533 
14534 				if (h)
14535 					o.content = h;
14536 			});
14537 		}
14538 
14539 		// Handle legacy onchange_callback option
14540 		if (settings.onchange_callback) {
14541 			self.onChange.add(function(ed, l) {
14542 				ed.execCallback('onchange_callback', ed, l);
14543 			});
14544 		}
14545 	};
14546 
14547 	tinymce.Editor.prototype.bindNativeEvents = function() {
14548 		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
14549 		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
14550 
14551 		nativeToDispatcherMap = {
14552 			mouseup : 'onMouseUp',
14553 			mousedown : 'onMouseDown',
14554 			click : 'onClick',
14555 			keyup : 'onKeyUp',
14556 			keydown : 'onKeyDown',
14557 			keypress : 'onKeyPress',
14558 			submit : 'onSubmit',
14559 			reset : 'onReset',
14560 			contextmenu : 'onContextMenu',
14561 			dblclick : 'onDblClick',
14562 			paste : 'onPaste' // Doesn't work in all browsers yet
14563 		};
14564 
14565 		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
14566 		function eventHandler(evt, args) {
14567 			var type = evt.type;
14568 
14569 			// Don't fire events when it's removed
14570 			if (self.removed)
14571 				return;
14572 
14573 			// Sends the native event out to a global dispatcher then to the specific event dispatcher
14574 			if (self.onEvent.dispatch(self, evt, args) !== false) {
14575 				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
14576 			}
14577 		};
14578 
14579 		// Opera doesn't support focus event for contentEditable elements so we need to fake it
14580 		function doOperaFocus(e) {
14581 			self.focus(true);
14582 		};
14583 
14584 		function nodeChanged(ed, e) {
14585 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
14586 			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
14587 				self.selection.normalize();
14588 			}
14589 
14590 			self.nodeChanged();
14591 		}
14592 
14593 		// Add DOM events
14594 		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
14595 			var root = settings.content_editable ? self.getBody() : self.getDoc();
14596 
14597 			switch (nativeName) {
14598 				case 'contextmenu':
14599 					dom.bind(root, nativeName, eventHandler);
14600 					break;
14601 
14602 				case 'paste':
14603 					dom.bind(self.getBody(), nativeName, eventHandler);
14604 					break;
14605 
14606 				case 'submit':
14607 				case 'reset':
14608 					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
14609 					break;
14610 
14611 				default:
14612 					dom.bind(root, nativeName, eventHandler);
14613 			}
14614 		});
14615 
14616 		// Set the editor as active when focused
14617 		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
14618 			self.focus(true);
14619 		});
14620 
14621 		if (settings.content_editable && tinymce.isOpera) {
14622 			dom.bind(self.getBody(), 'click', doOperaFocus);
14623 			dom.bind(self.getBody(), 'keydown', doOperaFocus);
14624 		}
14625 
14626 		// Add node change handler
14627 		self.onMouseUp.add(nodeChanged);
14628 
14629 		self.onKeyUp.add(function(ed, e) {
14630 			var keyCode = e.keyCode;
14631 
14632 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
14633 				nodeChanged(ed, e);
14634 		});
14635 
14636 		// Add reset handler
14637 		self.onReset.add(function() {
14638 			self.setContent(self.startContent, {format : 'raw'});
14639 		});
14640 
14641 		// Add shortcuts
14642 		function handleShortcut(e, execute) {
14643 			if (e.altKey || e.ctrlKey || e.metaKey) {
14644 				each(self.shortcuts, function(shortcut) {
14645 					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
14646 
14647 					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
14648 						return;
14649 
14650 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
14651 						e.preventDefault();
14652 
14653 						if (execute) {
14654 							shortcut.func.call(shortcut.scope);
14655 						}
14656 
14657 						return true;
14658 					}
14659 				});
14660 			}
14661 		};
14662 
14663 		self.onKeyUp.add(function(ed, e) {
14664 			handleShortcut(e);
14665 		});
14666 
14667 		self.onKeyPress.add(function(ed, e) {
14668 			handleShortcut(e);
14669 		});
14670 
14671 		self.onKeyDown.add(function(ed, e) {
14672 			handleShortcut(e, true);
14673 		});
14674 
14675 		if (tinymce.isOpera) {
14676 			self.onClick.add(function(ed, e) {
14677 				e.preventDefault();
14678 			});
14679 		}
14680 	};
14681 })(tinymce);
14682 (function(tinymce) {
14683 	// Added for compression purposes
14684 	var each = tinymce.each, undef, TRUE = true, FALSE = false;
14685 
14686 	tinymce.EditorCommands = function(editor) {
14687 		var dom = editor.dom,
14688 			selection = editor.selection,
14689 			commands = {state: {}, exec : {}, value : {}},
14690 			settings = editor.settings,
14691 			formatter = editor.formatter,
14692 			bookmark;
14693 
14694 		function execCommand(command, ui, value) {
14695 			var func;
14696 
14697 			command = command.toLowerCase();
14698 			if (func = commands.exec[command]) {
14699 				func(command, ui, value);
14700 				return TRUE;
14701 			}
14702 
14703 			return FALSE;
14704 		};
14705 
14706 		function queryCommandState(command) {
14707 			var func;
14708 
14709 			command = command.toLowerCase();
14710 			if (func = commands.state[command])
14711 				return func(command);
14712 
14713 			return -1;
14714 		};
14715 
14716 		function queryCommandValue(command) {
14717 			var func;
14718 
14719 			command = command.toLowerCase();
14720 			if (func = commands.value[command])
14721 				return func(command);
14722 
14723 			return FALSE;
14724 		};
14725 
14726 		function addCommands(command_list, type) {
14727 			type = type || 'exec';
14728 
14729 			each(command_list, function(callback, command) {
14730 				each(command.toLowerCase().split(','), function(command) {
14731 					commands[type][command] = callback;
14732 				});
14733 			});
14734 		};
14735 
14736 		// Expose public methods
14737 		tinymce.extend(this, {
14738 			execCommand : execCommand,
14739 			queryCommandState : queryCommandState,
14740 			queryCommandValue : queryCommandValue,
14741 			addCommands : addCommands
14742 		});
14743 
14744 		// Private methods
14745 
14746 		function execNativeCommand(command, ui, value) {
14747 			if (ui === undef)
14748 				ui = FALSE;
14749 
14750 			if (value === undef)
14751 				value = null;
14752 
14753 			return editor.getDoc().execCommand(command, ui, value);
14754 		};
14755 
14756 		function isFormatMatch(name) {
14757 			return formatter.match(name);
14758 		};
14759 
14760 		function toggleFormat(name, value) {
14761 			formatter.toggle(name, value ? {value : value} : undef);
14762 		};
14763 
14764 		function storeSelection(type) {
14765 			bookmark = selection.getBookmark(type);
14766 		};
14767 
14768 		function restoreSelection() {
14769 			selection.moveToBookmark(bookmark);
14770 		};
14771 
14772 		// Add execCommand overrides
14773 		addCommands({
14774 			// Ignore these, added for compatibility
14775 			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
14776 
14777 			// Add undo manager logic
14778 			'mceEndUndoLevel,mceAddUndoLevel' : function() {
14779 				editor.undoManager.add();
14780 			},
14781 
14782 			'Cut,Copy,Paste' : function(command) {
14783 				var doc = editor.getDoc(), failed;
14784 
14785 				// Try executing the native command
14786 				try {
14787 					execNativeCommand(command);
14788 				} catch (ex) {
14789 					// Command failed
14790 					failed = TRUE;
14791 				}
14792 
14793 				// Present alert message about clipboard access not being available
14794 				if (failed || !doc.queryCommandSupported(command)) {
14795 					if (tinymce.isGecko) {
14796 						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
14797 							if (state)
14798 								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
14799 						});
14800 					} else
14801 						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
14802 				}
14803 			},
14804 
14805 			// Override unlink command
14806 			unlink : function(command) {
14807 				if (selection.isCollapsed())
14808 					selection.select(selection.getNode());
14809 
14810 				execNativeCommand(command);
14811 				selection.collapse(FALSE);
14812 			},
14813 
14814 			// Override justify commands to use the text formatter engine
14815 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
14816 				var align = command.substring(7);
14817 
14818 				// Remove all other alignments first
14819 				each('left,center,right,full'.split(','), function(name) {
14820 					if (align != name)
14821 						formatter.remove('align' + name);
14822 				});
14823 
14824 				toggleFormat('align' + align);
14825 				execCommand('mceRepaint');
14826 			},
14827 
14828 			// Override list commands to fix WebKit bug
14829 			'InsertUnorderedList,InsertOrderedList' : function(command) {
14830 				var listElm, listParent;
14831 
14832 				execNativeCommand(command);
14833 
14834 				// WebKit produces lists within block elements so we need to split them
14835 				// we will replace the native list creation logic to custom logic later on
14836 				// TODO: Remove this when the list creation logic is removed
14837 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
14838 				if (listElm) {
14839 					listParent = listElm.parentNode;
14840 
14841 					// If list is within a text block then split that block
14842 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
14843 						storeSelection();
14844 						dom.split(listParent, listElm);
14845 						restoreSelection();
14846 					}
14847 				}
14848 			},
14849 
14850 			// Override commands to use the text formatter engine
14851 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
14852 				toggleFormat(command);
14853 			},
14854 
14855 			// Override commands to use the text formatter engine
14856 			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
14857 				toggleFormat(command, value);
14858 			},
14859 
14860 			FontSize : function(command, ui, value) {
14861 				var fontClasses, fontSizes;
14862 
14863 				// Convert font size 1-7 to styles
14864 				if (value >= 1 && value <= 7) {
14865 					fontSizes = tinymce.explode(settings.font_size_style_values);
14866 					fontClasses = tinymce.explode(settings.font_size_classes);
14867 
14868 					if (fontClasses)
14869 						value = fontClasses[value - 1] || value;
14870 					else
14871 						value = fontSizes[value - 1] || value;
14872 				}
14873 
14874 				toggleFormat(command, value);
14875 			},
14876 
14877 			RemoveFormat : function(command) {
14878 				formatter.remove(command);
14879 			},
14880 
14881 			mceBlockQuote : function(command) {
14882 				toggleFormat('blockquote');
14883 			},
14884 
14885 			FormatBlock : function(command, ui, value) {
14886 				return toggleFormat(value || 'p');
14887 			},
14888 
14889 			mceCleanup : function() {
14890 				var bookmark = selection.getBookmark();
14891 
14892 				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
14893 
14894 				selection.moveToBookmark(bookmark);
14895 			},
14896 
14897 			mceRemoveNode : function(command, ui, value) {
14898 				var node = value || selection.getNode();
14899 
14900 				// Make sure that the body node isn't removed
14901 				if (node != editor.getBody()) {
14902 					storeSelection();
14903 					editor.dom.remove(node, TRUE);
14904 					restoreSelection();
14905 				}
14906 			},
14907 
14908 			mceSelectNodeDepth : function(command, ui, value) {
14909 				var counter = 0;
14910 
14911 				dom.getParent(selection.getNode(), function(node) {
14912 					if (node.nodeType == 1 && counter++ == value) {
14913 						selection.select(node);
14914 						return FALSE;
14915 					}
14916 				}, editor.getBody());
14917 			},
14918 
14919 			mceSelectNode : function(command, ui, value) {
14920 				selection.select(value);
14921 			},
14922 
14923 			mceInsertContent : function(command, ui, value) {
14924 				var parser, serializer, parentNode, rootNode, fragment, args,
14925 					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
14926 
14927 				//selection.normalize();
14928 
14929 				// Setup parser and serializer
14930 				parser = editor.parser;
14931 				serializer = new tinymce.html.Serializer({}, editor.schema);
14932 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
14933 
14934 				// Run beforeSetContent handlers on the HTML to be inserted
14935 				args = {content: value, format: 'html'};
14936 				selection.onBeforeSetContent.dispatch(selection, args);
14937 				value = args.content;
14938 
14939 				// Add caret at end of contents if it's missing
14940 				if (value.indexOf('{$caret}') == -1)
14941 					value += '{$caret}';
14942 
14943 				// Replace the caret marker with a span bookmark element
14944 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
14945 
14946 				// Insert node maker where we will insert the new HTML and get it's parent
14947 				if (!selection.isCollapsed())
14948 					editor.getDoc().execCommand('Delete', false, null);
14949 
14950 				parentNode = selection.getNode();
14951 
14952 				// Parse the fragment within the context of the parent node
14953 				args = {context : parentNode.nodeName.toLowerCase()};
14954 				fragment = parser.parse(value, args);
14955 
14956 				// Move the caret to a more suitable location
14957 				node = fragment.lastChild;
14958 				if (node.attr('id') == 'mce_marker') {
14959 					marker = node;
14960 
14961 					for (node = node.prev; node; node = node.walk(true)) {
14962 						if (node.type == 3 || !dom.isBlock(node.name)) {
14963 							node.parent.insert(marker, node, node.name === 'br');
14964 							break;
14965 						}
14966 					}
14967 				}
14968 
14969 				// If parser says valid we can insert the contents into that parent
14970 				if (!args.invalid) {
14971 					value = serializer.serialize(fragment);
14972 
14973 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
14974 					node = parentNode.firstChild;
14975 					node2 = parentNode.lastChild;
14976 					if (!node || (node === node2 && node.nodeName === 'BR'))
14977 						dom.setHTML(parentNode, value);
14978 					else
14979 						selection.setContent(value);
14980 				} else {
14981 					// If the fragment was invalid within that context then we need
14982 					// to parse and process the parent it's inserted into
14983 
14984 					// Insert bookmark node and get the parent
14985 					selection.setContent(bookmarkHtml);
14986 					parentNode = editor.selection.getNode();
14987 					rootNode = editor.getBody();
14988 
14989 					// Opera will return the document node when selection is in root
14990 					if (parentNode.nodeType == 9)
14991 						parentNode = node = rootNode;
14992 					else
14993 						node = parentNode;
14994 
14995 					// Find the ancestor just before the root element
14996 					while (node !== rootNode) {
14997 						parentNode = node;
14998 						node = node.parentNode;
14999 					}
15000 
15001 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
15002 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
15003 					value = serializer.serialize(
15004 						parser.parse(
15005 							// Need to replace by using a function since $ in the contents would otherwise be a problem
15006 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
15007 								return serializer.serialize(fragment);
15008 							})
15009 						)
15010 					);
15011 
15012 					// Set the inner/outer HTML depending on if we are in the root or not
15013 					if (parentNode == rootNode)
15014 						dom.setHTML(rootNode, value);
15015 					else
15016 						dom.setOuterHTML(parentNode, value);
15017 				}
15018 
15019 				marker = dom.get('mce_marker');
15020 
15021 				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15022 				nodeRect = dom.getRect(marker);
15023 				viewPortRect = dom.getViewPort(editor.getWin());
15024 
15025 				// Check if node is out side the viewport if it is then scroll to it
15026 				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15027 					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15028 					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
15029 					viewportBodyElement.scrollLeft = nodeRect.x;
15030 					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15031 				}
15032 
15033 				// Move selection before marker and remove it
15034 				rng = dom.createRng();
15035 
15036 				// If previous sibling is a text node set the selection to the end of that node
15037 				node = marker.previousSibling;
15038 				if (node && node.nodeType == 3) {
15039 					rng.setStart(node, node.nodeValue.length);
15040 				} else {
15041 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15042 					rng.setStartBefore(marker);
15043 					rng.setEndBefore(marker);
15044 				}
15045 
15046 				// Remove the marker node and set the new range
15047 				dom.remove(marker);
15048 				selection.setRng(rng);
15049 
15050 				// Dispatch after event and add any visual elements needed
15051 				selection.onSetContent.dispatch(selection, args);
15052 				editor.addVisual();
15053 			},
15054 
15055 			mceInsertRawHTML : function(command, ui, value) {
15056 				selection.setContent('tiny_mce_marker');
15057 				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
15058 			},
15059 
15060 			mceToggleFormat : function(command, ui, value) {
15061 				toggleFormat(value);
15062 			},
15063 
15064 			mceSetContent : function(command, ui, value) {
15065 				editor.setContent(value);
15066 			},
15067 
15068 			'Indent,Outdent' : function(command) {
15069 				var intentValue, indentUnit, value;
15070 
15071 				// Setup indent level
15072 				intentValue = settings.indentation;
15073 				indentUnit = /[a-z%]+$/i.exec(intentValue);
15074 				intentValue = parseInt(intentValue);
15075 
15076 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15077 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15078 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15079 						formatter.apply('div');
15080 					}
15081 
15082 					each(selection.getSelectedBlocks(), function(element) {
15083 						if (command == 'outdent') {
15084 							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
15085 							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
15086 						} else
15087 							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
15088 					});
15089 				} else
15090 					execNativeCommand(command);
15091 			},
15092 
15093 			mceRepaint : function() {
15094 				var bookmark;
15095 
15096 				if (tinymce.isGecko) {
15097 					try {
15098 						storeSelection(TRUE);
15099 
15100 						if (selection.getSel())
15101 							selection.getSel().selectAllChildren(editor.getBody());
15102 
15103 						selection.collapse(TRUE);
15104 						restoreSelection();
15105 					} catch (ex) {
15106 						// Ignore
15107 					}
15108 				}
15109 			},
15110 
15111 			mceToggleFormat : function(command, ui, value) {
15112 				formatter.toggle(value);
15113 			},
15114 
15115 			InsertHorizontalRule : function() {
15116 				editor.execCommand('mceInsertContent', false, '<hr />');
15117 			},
15118 
15119 			mceToggleVisualAid : function() {
15120 				editor.hasVisual = !editor.hasVisual;
15121 				editor.addVisual();
15122 			},
15123 
15124 			mceReplaceContent : function(command, ui, value) {
15125 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
15126 			},
15127 
15128 			mceInsertLink : function(command, ui, value) {
15129 				var anchor;
15130 
15131 				if (typeof(value) == 'string')
15132 					value = {href : value};
15133 
15134 				anchor = dom.getParent(selection.getNode(), 'a');
15135 
15136 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15137 				value.href = value.href.replace(' ', '%20');
15138 
15139 				// Remove existing links if there could be child links or that the href isn't specified
15140 				if (!anchor || !value.href) {
15141 					formatter.remove('link');
15142 				}		
15143 
15144 				// Apply new link to selection
15145 				if (value.href) {
15146 					formatter.apply('link', value, anchor);
15147 				}
15148 			},
15149 
15150 			selectAll : function() {
15151 				var root = dom.getRoot(), rng = dom.createRng();
15152 
15153 				rng.setStart(root, 0);
15154 				rng.setEnd(root, root.childNodes.length);
15155 
15156 				editor.selection.setRng(rng);
15157 			}
15158 		});
15159 
15160 		// Add queryCommandState overrides
15161 		addCommands({
15162 			// Override justify commands
15163 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15164 				var name = 'align' + command.substring(7);
15165 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15166 				var matches = tinymce.map(nodes, function(node) {
15167 					return !!formatter.matchNode(node, name);
15168 				});
15169 				return tinymce.inArray(matches, TRUE) !== -1;
15170 			},
15171 
15172 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15173 				return isFormatMatch(command);
15174 			},
15175 
15176 			mceBlockQuote : function() {
15177 				return isFormatMatch('blockquote');
15178 			},
15179 
15180 			Outdent : function() {
15181 				var node;
15182 
15183 				if (settings.inline_styles) {
15184 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15185 						return TRUE;
15186 
15187 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15188 						return TRUE;
15189 				}
15190 
15191 				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
15192 			},
15193 
15194 			'InsertUnorderedList,InsertOrderedList' : function(command) {
15195 				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
15196 			}
15197 		}, 'state');
15198 
15199 		// Add queryCommandValue overrides
15200 		addCommands({
15201 			'FontSize,FontName' : function(command) {
15202 				var value = 0, parent;
15203 
15204 				if (parent = dom.getParent(selection.getNode(), 'span')) {
15205 					if (command == 'fontsize')
15206 						value = parent.style.fontSize;
15207 					else
15208 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15209 				}
15210 
15211 				return value;
15212 			}
15213 		}, 'value');
15214 
15215 		// Add undo manager logic
15216 		addCommands({
15217 			Undo : function() {
15218 				editor.undoManager.undo();
15219 			},
15220 
15221 			Redo : function() {
15222 				editor.undoManager.redo();
15223 			}
15224 		});
15225 	};
15226 })(tinymce);
15227 
15228 (function(tinymce) {
15229 	var Dispatcher = tinymce.util.Dispatcher;
15230 
15231 	tinymce.UndoManager = function(editor) {
15232 		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
15233 
15234 		function getContent() {
15235 			// Remove whitespace before/after and remove pure bogus nodes
15236 			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
15237 		};
15238 
15239 		function addNonTypingUndoLevel() {
15240 			self.typing = false;
15241 			self.add();
15242 		};
15243 
15244 		// Create event instances
15245 		onBeforeAdd = new Dispatcher(self);
15246 		onAdd       = new Dispatcher(self);
15247 		onUndo      = new Dispatcher(self);
15248 		onRedo      = new Dispatcher(self);
15249 
15250 		// Pass though onAdd event from UndoManager to Editor as onChange
15251 		onAdd.add(function(undoman, level) {
15252 			if (undoman.hasUndo())
15253 				return editor.onChange.dispatch(editor, level, undoman);
15254 		});
15255 
15256 		// Pass though onUndo event from UndoManager to Editor
15257 		onUndo.add(function(undoman, level) {
15258 			return editor.onUndo.dispatch(editor, level, undoman);
15259 		});
15260 
15261 		// Pass though onRedo event from UndoManager to Editor
15262 		onRedo.add(function(undoman, level) {
15263 			return editor.onRedo.dispatch(editor, level, undoman);
15264 		});
15265 
15266 		// Add initial undo level when the editor is initialized
15267 		editor.onInit.add(function() {
15268 			self.add();
15269 		});
15270 
15271 		// Get position before an execCommand is processed
15272 		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
15273 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15274 				self.beforeChange();
15275 			}
15276 		});
15277 
15278 		// Add undo level after an execCommand call was made
15279 		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
15280 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15281 				self.add();
15282 			}
15283 		});
15284 
15285 		// Add undo level on save contents, drag end and blur/focusout
15286 		editor.onSaveContent.add(addNonTypingUndoLevel);
15287 		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15288 		editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
15289 			if (!editor.removed && self.typing) {
15290 				addNonTypingUndoLevel();
15291 			}
15292 		});
15293 
15294 		editor.onKeyUp.add(function(editor, e) {
15295 			var keyCode = e.keyCode;
15296 
15297 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
15298 				addNonTypingUndoLevel();
15299 			}
15300 		});
15301 
15302 		editor.onKeyDown.add(function(editor, e) {
15303 			var keyCode = e.keyCode;
15304 
15305 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
15306 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
15307 				if (self.typing) {
15308 					addNonTypingUndoLevel();
15309 				}
15310 
15311 				return;
15312 			}
15313 
15314 			// If key isn't shift,ctrl,alt,capslock,metakey
15315 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
15316 				self.beforeChange();
15317 				self.typing = true;
15318 				self.add();
15319 			}
15320 		});
15321 
15322 		editor.onMouseDown.add(function(editor, e) {
15323 			if (self.typing) {
15324 				addNonTypingUndoLevel();
15325 			}
15326 		});
15327 
15328 		// Add keyboard shortcuts for undo/redo keys
15329 		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
15330 		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
15331 
15332 		self = {
15333 			// Explose for debugging reasons
15334 			data : data,
15335 
15336 			typing : false,
15337 			
15338 			onBeforeAdd: onBeforeAdd,
15339 
15340 			onAdd : onAdd,
15341 
15342 			onUndo : onUndo,
15343 
15344 			onRedo : onRedo,
15345 
15346 			beforeChange : function() {
15347 				beforeBookmark = editor.selection.getBookmark(2, true);
15348 			},
15349 
15350 			add : function(level) {
15351 				var i, settings = editor.settings, lastLevel;
15352 
15353 				level = level || {};
15354 				level.content = getContent();
15355 				
15356 				self.onBeforeAdd.dispatch(self, level);
15357 
15358 				// Add undo level if needed
15359 				lastLevel = data[index];
15360 				if (lastLevel && lastLevel.content == level.content)
15361 					return null;
15362 
15363 				// Set before bookmark on previous level
15364 				if (data[index])
15365 					data[index].beforeBookmark = beforeBookmark;
15366 
15367 				// Time to compress
15368 				if (settings.custom_undo_redo_levels) {
15369 					if (data.length > settings.custom_undo_redo_levels) {
15370 						for (i = 0; i < data.length - 1; i++)
15371 							data[i] = data[i + 1];
15372 
15373 						data.length--;
15374 						index = data.length;
15375 					}
15376 				}
15377 
15378 				// Get a non intrusive normalized bookmark
15379 				level.bookmark = editor.selection.getBookmark(2, true);
15380 
15381 				// Crop array if needed
15382 				if (index < data.length - 1)
15383 					data.length = index + 1;
15384 
15385 				data.push(level);
15386 				index = data.length - 1;
15387 
15388 				self.onAdd.dispatch(self, level);
15389 				editor.isNotDirty = 0;
15390 
15391 				return level;
15392 			},
15393 
15394 			undo : function() {
15395 				var level, i;
15396 
15397 				if (self.typing) {
15398 					self.add();
15399 					self.typing = false;
15400 				}
15401 
15402 				if (index > 0) {
15403 					level = data[--index];
15404 
15405 					editor.setContent(level.content, {format : 'raw'});
15406 					editor.selection.moveToBookmark(level.beforeBookmark);
15407 
15408 					self.onUndo.dispatch(self, level);
15409 				}
15410 
15411 				return level;
15412 			},
15413 
15414 			redo : function() {
15415 				var level;
15416 
15417 				if (index < data.length - 1) {
15418 					level = data[++index];
15419 
15420 					editor.setContent(level.content, {format : 'raw'});
15421 					editor.selection.moveToBookmark(level.bookmark);
15422 
15423 					self.onRedo.dispatch(self, level);
15424 				}
15425 
15426 				return level;
15427 			},
15428 
15429 			clear : function() {
15430 				data = [];
15431 				index = 0;
15432 				self.typing = false;
15433 			},
15434 
15435 			hasUndo : function() {
15436 				return index > 0 || this.typing;
15437 			},
15438 
15439 			hasRedo : function() {
15440 				return index < data.length - 1 && !this.typing;
15441 			}
15442 		};
15443 
15444 		return self;
15445 	};
15446 })(tinymce);
15447 
15448 tinymce.ForceBlocks = function(editor) {
15449 	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
15450 
15451 	function addRootBlocks() {
15452 		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
15453 
15454 		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
15455 			return;
15456 
15457 		// Check if node is wrapped in block
15458 		while (node && node != rootNode) {
15459 			if (blockElements[node.nodeName])
15460 				return;
15461 
15462 			node = node.parentNode;
15463 		}
15464 
15465 		// Get current selection
15466 		rng = selection.getRng();
15467 		if (rng.setStart) {
15468 			startContainer = rng.startContainer;
15469 			startOffset = rng.startOffset;
15470 			endContainer = rng.endContainer;
15471 			endOffset = rng.endOffset;
15472 		} else {
15473 			// Force control range into text range
15474 			if (rng.item) {
15475 				node = rng.item(0);
15476 				rng = editor.getDoc().body.createTextRange();
15477 				rng.moveToElementText(node);
15478 			}
15479 
15480 			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
15481 			tmpRng = rng.duplicate();
15482 			tmpRng.collapse(true);
15483 			startOffset = tmpRng.move('character', offset) * -1;
15484 
15485 			if (!tmpRng.collapsed) {
15486 				tmpRng = rng.duplicate();
15487 				tmpRng.collapse(false);
15488 				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
15489 			}
15490 		}
15491 
15492 		// Wrap non block elements and text nodes
15493 		node = rootNode.firstChild;
15494 		while (node) {
15495 			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
15496 				if (!rootBlockNode) {
15497 					rootBlockNode = dom.create(settings.forced_root_block);
15498 					node.parentNode.insertBefore(rootBlockNode, node);
15499 					wrapped = true;
15500 				}
15501 
15502 				tempNode = node;
15503 				node = node.nextSibling;
15504 				rootBlockNode.appendChild(tempNode);
15505 			} else {
15506 				rootBlockNode = null;
15507 				node = node.nextSibling;
15508 			}
15509 		}
15510 
15511 		if (wrapped) {
15512 			if (rng.setStart) {
15513 				rng.setStart(startContainer, startOffset);
15514 				rng.setEnd(endContainer, endOffset);
15515 				selection.setRng(rng);
15516 			} else {
15517 				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
15518 				if (isInEditorDocument) {
15519 					try {
15520 						rng = editor.getDoc().body.createTextRange();
15521 						rng.moveToElementText(rootNode);
15522 						rng.collapse(true);
15523 						rng.moveStart('character', startOffset);
15524 
15525 						if (endOffset > 0)
15526 							rng.moveEnd('character', endOffset);
15527 
15528 						rng.select();
15529 					} catch (ex) {
15530 						// Ignore
15531 					}
15532 				}
15533 			}
15534 
15535 			editor.nodeChanged();
15536 		}
15537 	};
15538 
15539 	// Force root blocks
15540 	if (settings.forced_root_block) {
15541 		editor.onKeyUp.add(addRootBlocks);
15542 		editor.onNodeChange.add(addRootBlocks);
15543 	}
15544 };
15545 
15546 (function(tinymce) {
15547 	// Shorten names
15548 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15549 
15550 	tinymce.create('tinymce.ControlManager', {
15551 		ControlManager : function(ed, s) {
15552 			var t = this, i;
15553 
15554 			s = s || {};
15555 			t.editor = ed;
15556 			t.controls = {};
15557 			t.onAdd = new tinymce.util.Dispatcher(t);
15558 			t.onPostRender = new tinymce.util.Dispatcher(t);
15559 			t.prefix = s.prefix || ed.id + '_';
15560 			t._cls = {};
15561 
15562 			t.onPostRender.add(function() {
15563 				each(t.controls, function(c) {
15564 					c.postRender();
15565 				});
15566 			});
15567 		},
15568 
15569 		get : function(id) {
15570 			return this.controls[this.prefix + id] || this.controls[id];
15571 		},
15572 
15573 		setActive : function(id, s) {
15574 			var c = null;
15575 
15576 			if (c = this.get(id))
15577 				c.setActive(s);
15578 
15579 			return c;
15580 		},
15581 
15582 		setDisabled : function(id, s) {
15583 			var c = null;
15584 
15585 			if (c = this.get(id))
15586 				c.setDisabled(s);
15587 
15588 			return c;
15589 		},
15590 
15591 		add : function(c) {
15592 			var t = this;
15593 
15594 			if (c) {
15595 				t.controls[c.id] = c;
15596 				t.onAdd.dispatch(c, t);
15597 			}
15598 
15599 			return c;
15600 		},
15601 
15602 		createControl : function(name) {
15603 			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
15604 
15605 			// Build control factory cache
15606 			if (!self.controlFactories) {
15607 				self.controlFactories = [];
15608 				each(editor.plugins, function(plugin) {
15609 					if (plugin.createControl) {
15610 						self.controlFactories.push(plugin);
15611 					}
15612 				});
15613 			}
15614 
15615 			// Create controls by asking cached factories
15616 			factories = self.controlFactories;
15617 			for (i = 0, l = factories.length; i < l; i++) {
15618 				ctrl = factories[i].createControl(name, self);
15619 
15620 				if (ctrl) {
15621 					return self.add(ctrl);
15622 				}
15623 			}
15624 
15625 			// Create sepearator
15626 			if (name === "|" || name === "separator") {
15627 				return self.createSeparator();
15628 			}
15629 
15630 			// Create control from button collection
15631 			if (editor.buttons && (ctrl = editor.buttons[name])) {
15632 				return self.createButton(name, ctrl);
15633 			}
15634 
15635 			return self.add(ctrl);
15636 		},
15637 
15638 		createDropMenu : function(id, s, cc) {
15639 			var t = this, ed = t.editor, c, bm, v, cls;
15640 
15641 			s = extend({
15642 				'class' : 'mceDropDown',
15643 				constrain : ed.settings.constrain_menus
15644 			}, s);
15645 
15646 			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
15647 			if (v = ed.getParam('skin_variant'))
15648 				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
15649 
15650 			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
15651 
15652 			id = t.prefix + id;
15653 			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
15654 			c = t.controls[id] = new cls(id, s);
15655 			c.onAddItem.add(function(c, o) {
15656 				var s = o.settings;
15657 
15658 				s.title = ed.getLang(s.title, s.title);
15659 
15660 				if (!s.onclick) {
15661 					s.onclick = function(v) {
15662 						if (s.cmd)
15663 							ed.execCommand(s.cmd, s.ui || false, s.value);
15664 					};
15665 				}
15666 			});
15667 
15668 			ed.onRemove.add(function() {
15669 				c.destroy();
15670 			});
15671 
15672 			// Fix for bug #1897785, #1898007
15673 			if (tinymce.isIE) {
15674 				c.onShowMenu.add(function() {
15675 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15676 					ed.focus();
15677 
15678 					bm = ed.selection.getBookmark(1);
15679 				});
15680 
15681 				c.onHideMenu.add(function() {
15682 					if (bm) {
15683 						ed.selection.moveToBookmark(bm);
15684 						bm = 0;
15685 					}
15686 				});
15687 			}
15688 
15689 			return t.add(c);
15690 		},
15691 
15692 		createListBox : function(id, s, cc) {
15693 			var t = this, ed = t.editor, cmd, c, cls;
15694 
15695 			if (t.get(id))
15696 				return null;
15697 
15698 			s.title = ed.translate(s.title);
15699 			s.scope = s.scope || ed;
15700 
15701 			if (!s.onselect) {
15702 				s.onselect = function(v) {
15703 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15704 				};
15705 			}
15706 
15707 			s = extend({
15708 				title : s.title,
15709 				'class' : 'mce_' + id,
15710 				scope : s.scope,
15711 				control_manager : t
15712 			}, s);
15713 
15714 			id = t.prefix + id;
15715 
15716 
15717 			function useNativeListForAccessibility(ed) {
15718 				return ed.settings.use_accessible_selects && !tinymce.isGecko
15719 			}
15720 
15721 			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
15722 				c = new tinymce.ui.NativeListBox(id, s);
15723 			else {
15724 				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
15725 				c = new cls(id, s, ed);
15726 			}
15727 
15728 			t.controls[id] = c;
15729 
15730 			// Fix focus problem in Safari
15731 			if (tinymce.isWebKit) {
15732 				c.onPostRender.add(function(c, n) {
15733 					// Store bookmark on mousedown
15734 					Event.add(n, 'mousedown', function() {
15735 						ed.bookmark = ed.selection.getBookmark(1);
15736 					});
15737 
15738 					// Restore on focus, since it might be lost
15739 					Event.add(n, 'focus', function() {
15740 						ed.selection.moveToBookmark(ed.bookmark);
15741 						ed.bookmark = null;
15742 					});
15743 				});
15744 			}
15745 
15746 			if (c.hideMenu)
15747 				ed.onMouseDown.add(c.hideMenu, c);
15748 
15749 			return t.add(c);
15750 		},
15751 
15752 		createButton : function(id, s, cc) {
15753 			var t = this, ed = t.editor, o, c, cls;
15754 
15755 			if (t.get(id))
15756 				return null;
15757 
15758 			s.title = ed.translate(s.title);
15759 			s.label = ed.translate(s.label);
15760 			s.scope = s.scope || ed;
15761 
15762 			if (!s.onclick && !s.menu_button) {
15763 				s.onclick = function() {
15764 					ed.execCommand(s.cmd, s.ui || false, s.value);
15765 				};
15766 			}
15767 
15768 			s = extend({
15769 				title : s.title,
15770 				'class' : 'mce_' + id,
15771 				unavailable_prefix : ed.getLang('unavailable', ''),
15772 				scope : s.scope,
15773 				control_manager : t
15774 			}, s);
15775 
15776 			id = t.prefix + id;
15777 
15778 			if (s.menu_button) {
15779 				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
15780 				c = new cls(id, s, ed);
15781 				ed.onMouseDown.add(c.hideMenu, c);
15782 			} else {
15783 				cls = t._cls.button || tinymce.ui.Button;
15784 				c = new cls(id, s, ed);
15785 			}
15786 
15787 			return t.add(c);
15788 		},
15789 
15790 		createMenuButton : function(id, s, cc) {
15791 			s = s || {};
15792 			s.menu_button = 1;
15793 
15794 			return this.createButton(id, s, cc);
15795 		},
15796 
15797 		createSplitButton : function(id, s, cc) {
15798 			var t = this, ed = t.editor, cmd, c, cls;
15799 
15800 			if (t.get(id))
15801 				return null;
15802 
15803 			s.title = ed.translate(s.title);
15804 			s.scope = s.scope || ed;
15805 
15806 			if (!s.onclick) {
15807 				s.onclick = function(v) {
15808 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15809 				};
15810 			}
15811 
15812 			if (!s.onselect) {
15813 				s.onselect = function(v) {
15814 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15815 				};
15816 			}
15817 
15818 			s = extend({
15819 				title : s.title,
15820 				'class' : 'mce_' + id,
15821 				scope : s.scope,
15822 				control_manager : t
15823 			}, s);
15824 
15825 			id = t.prefix + id;
15826 			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
15827 			c = t.add(new cls(id, s, ed));
15828 			ed.onMouseDown.add(c.hideMenu, c);
15829 
15830 			return c;
15831 		},
15832 
15833 		createColorSplitButton : function(id, s, cc) {
15834 			var t = this, ed = t.editor, cmd, c, cls, bm;
15835 
15836 			if (t.get(id))
15837 				return null;
15838 
15839 			s.title = ed.translate(s.title);
15840 			s.scope = s.scope || ed;
15841 
15842 			if (!s.onclick) {
15843 				s.onclick = function(v) {
15844 					if (tinymce.isIE)
15845 						bm = ed.selection.getBookmark(1);
15846 
15847 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15848 				};
15849 			}
15850 
15851 			if (!s.onselect) {
15852 				s.onselect = function(v) {
15853 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15854 				};
15855 			}
15856 
15857 			s = extend({
15858 				title : s.title,
15859 				'class' : 'mce_' + id,
15860 				'menu_class' : ed.getParam('skin') + 'Skin',
15861 				scope : s.scope,
15862 				more_colors_title : ed.getLang('more_colors')
15863 			}, s);
15864 
15865 			id = t.prefix + id;
15866 			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
15867 			c = new cls(id, s, ed);
15868 			ed.onMouseDown.add(c.hideMenu, c);
15869 
15870 			// Remove the menu element when the editor is removed
15871 			ed.onRemove.add(function() {
15872 				c.destroy();
15873 			});
15874 
15875 			// Fix for bug #1897785, #1898007
15876 			if (tinymce.isIE) {
15877 				c.onShowMenu.add(function() {
15878 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15879 					ed.focus();
15880 					bm = ed.selection.getBookmark(1);
15881 				});
15882 
15883 				c.onHideMenu.add(function() {
15884 					if (bm) {
15885 						ed.selection.moveToBookmark(bm);
15886 						bm = 0;
15887 					}
15888 				});
15889 			}
15890 
15891 			return t.add(c);
15892 		},
15893 
15894 		createToolbar : function(id, s, cc) {
15895 			var c, t = this, cls;
15896 
15897 			id = t.prefix + id;
15898 			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
15899 			c = new cls(id, s, t.editor);
15900 
15901 			if (t.get(id))
15902 				return null;
15903 
15904 			return t.add(c);
15905 		},
15906 		
15907 		createToolbarGroup : function(id, s, cc) {
15908 			var c, t = this, cls;
15909 			id = t.prefix + id;
15910 			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
15911 			c = new cls(id, s, t.editor);
15912 			
15913 			if (t.get(id))
15914 				return null;
15915 			
15916 			return t.add(c);
15917 		},
15918 
15919 		createSeparator : function(cc) {
15920 			var cls = cc || this._cls.separator || tinymce.ui.Separator;
15921 
15922 			return new cls();
15923 		},
15924 
15925 		setControlType : function(n, c) {
15926 			return this._cls[n.toLowerCase()] = c;
15927 		},
15928 	
15929 		destroy : function() {
15930 			each(this.controls, function(c) {
15931 				c.destroy();
15932 			});
15933 
15934 			this.controls = null;
15935 		}
15936 	});
15937 })(tinymce);
15938 
15939 (function(tinymce) {
15940 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
15941 
15942 	tinymce.create('tinymce.WindowManager', {
15943 		WindowManager : function(ed) {
15944 			var t = this;
15945 
15946 			t.editor = ed;
15947 			t.onOpen = new Dispatcher(t);
15948 			t.onClose = new Dispatcher(t);
15949 			t.params = {};
15950 			t.features = {};
15951 		},
15952 
15953 		open : function(s, p) {
15954 			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
15955 
15956 			// Default some options
15957 			s = s || {};
15958 			p = p || {};
15959 			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
15960 			sh = isOpera ? vp.h : screen.height;
15961 			s.name = s.name || 'mc_' + new Date().getTime();
15962 			s.width = parseInt(s.width || 320);
15963 			s.height = parseInt(s.height || 240);
15964 			s.resizable = true;
15965 			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
15966 			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
15967 			p.inline = false;
15968 			p.mce_width = s.width;
15969 			p.mce_height = s.height;
15970 			p.mce_auto_focus = s.auto_focus;
15971 
15972 			if (mo) {
15973 				if (isIE) {
15974 					s.center = true;
15975 					s.help = false;
15976 					s.dialogWidth = s.width + 'px';
15977 					s.dialogHeight = s.height + 'px';
15978 					s.scroll = s.scrollbars || false;
15979 				}
15980 			}
15981 
15982 			// Build features string
15983 			each(s, function(v, k) {
15984 				if (tinymce.is(v, 'boolean'))
15985 					v = v ? 'yes' : 'no';
15986 
15987 				if (!/^(name|url)$/.test(k)) {
15988 					if (isIE && mo)
15989 						f += (f ? ';' : '') + k + ':' + v;
15990 					else
15991 						f += (f ? ',' : '') + k + '=' + v;
15992 				}
15993 			});
15994 
15995 			t.features = s;
15996 			t.params = p;
15997 			t.onOpen.dispatch(t, s, p);
15998 
15999 			u = s.url || s.file;
16000 			u = tinymce._addVer(u);
16001 
16002 			try {
16003 				if (isIE && mo) {
16004 					w = 1;
16005 					window.showModalDialog(u, window, f);
16006 				} else
16007 					w = window.open(u, s.name, f);
16008 			} catch (ex) {
16009 				// Ignore
16010 			}
16011 
16012 			if (!w)
16013 				alert(t.editor.getLang('popup_blocked'));
16014 		},
16015 
16016 		close : function(w) {
16017 			w.close();
16018 			this.onClose.dispatch(this);
16019 		},
16020 
16021 		createInstance : function(cl, a, b, c, d, e) {
16022 			var f = tinymce.resolve(cl);
16023 
16024 			return new f(a, b, c, d, e);
16025 		},
16026 
16027 		confirm : function(t, cb, s, w) {
16028 			w = w || window;
16029 
16030 			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
16031 		},
16032 
16033 		alert : function(tx, cb, s, w) {
16034 			var t = this;
16035 
16036 			w = w || window;
16037 			w.alert(t._decode(t.editor.getLang(tx, tx)));
16038 
16039 			if (cb)
16040 				cb.call(s || t);
16041 		},
16042 
16043 		resizeBy : function(dw, dh, win) {
16044 			win.resizeBy(dw, dh);
16045 		},
16046 
16047 		// Internal functions
16048 
16049 		_decode : function(s) {
16050 			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
16051 		}
16052 	});
16053 }(tinymce));
16054 (function(tinymce) {
16055 	tinymce.Formatter = function(ed) {
16056 		var formats = {},
16057 			each = tinymce.each,
16058 			dom = ed.dom,
16059 			selection = ed.selection,
16060 			TreeWalker = tinymce.dom.TreeWalker,
16061 			rangeUtils = new tinymce.dom.RangeUtils(dom),
16062 			isValid = ed.schema.isValidChild,
16063 			isBlock = dom.isBlock,
16064 			forcedRootBlock = ed.settings.forced_root_block,
16065 			nodeIndex = dom.nodeIndex,
16066 			INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
16067 			MCE_ATTR_RE = /^(src|href|style)$/,
16068 			FALSE = false,
16069 			TRUE = true,
16070 			formatChangeData,
16071 			undef,
16072 			getContentEditable = dom.getContentEditable;
16073 
16074 		function isArray(obj) {
16075 			return obj instanceof Array;
16076 		};
16077 
16078 		function getParents(node, selector) {
16079 			return dom.getParents(node, selector, dom.getRoot());
16080 		};
16081 
16082 		function isCaretNode(node) {
16083 			return node.nodeType === 1 && node.id === '_mce_caret';
16084 		};
16085 
16086 		function defaultFormats() {
16087 			register({
16088 				alignleft : [
16089 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
16090 					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
16091 				],
16092 
16093 				aligncenter : [
16094 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16095 					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
16096 					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
16097 				],
16098 
16099 				alignright : [
16100 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
16101 					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
16102 				],
16103 
16104 				alignfull : [
16105 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
16106 				],
16107 
16108 				bold : [
16109 					{inline : 'strong', remove : 'all'},
16110 					{inline : 'span', styles : {fontWeight : 'bold'}},
16111 					{inline : 'b', remove : 'all'}
16112 				],
16113 
16114 				italic : [
16115 					{inline : 'em', remove : 'all'},
16116 					{inline : 'span', styles : {fontStyle : 'italic'}},
16117 					{inline : 'i', remove : 'all'}
16118 				],
16119 
16120 				underline : [
16121 					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
16122 					{inline : 'u', remove : 'all'}
16123 				],
16124 
16125 				strikethrough : [
16126 					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
16127 					{inline : 'strike', remove : 'all'}
16128 				],
16129 
16130 				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
16131 				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
16132 				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
16133 				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
16134 				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
16135 				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
16136 				subscript : {inline : 'sub'},
16137 				superscript : {inline : 'sup'},
16138 
16139 				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
16140 					onmatch : function(node) {
16141 						return true;
16142 					},
16143 
16144 					onformat : function(elm, fmt, vars) {
16145 						each(vars, function(value, key) {
16146 							dom.setAttrib(elm, key, value);
16147 						});
16148 					}
16149 				},
16150 
16151 				removeformat : [
16152 					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
16153 					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
16154 					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
16155 				]
16156 			});
16157 
16158 			// Register default block formats
16159 			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
16160 				register(name, {block : name, remove : 'all'});
16161 			});
16162 
16163 			// Register user defined formats
16164 			register(ed.settings.formats);
16165 		};
16166 
16167 		function addKeyboardShortcuts() {
16168 			// Add some inline shortcuts
16169 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
16170 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
16171 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
16172 
16173 			// BlockFormat shortcuts keys
16174 			for (var i = 1; i <= 6; i++) {
16175 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
16176 			}
16177 
16178 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
16179 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
16180 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
16181 		};
16182 
16183 		// Public functions
16184 
16185 		function get(name) {
16186 			return name ? formats[name] : formats;
16187 		};
16188 
16189 		function register(name, format) {
16190 			if (name) {
16191 				if (typeof(name) !== 'string') {
16192 					each(name, function(format, name) {
16193 						register(name, format);
16194 					});
16195 				} else {
16196 					// Force format into array and add it to internal collection
16197 					format = format.length ? format : [format];
16198 
16199 					each(format, function(format) {
16200 						// Set deep to false by default on selector formats this to avoid removing
16201 						// alignment on images inside paragraphs when alignment is changed on paragraphs
16202 						if (format.deep === undef)
16203 							format.deep = !format.selector;
16204 
16205 						// Default to true
16206 						if (format.split === undef)
16207 							format.split = !format.selector || format.inline;
16208 
16209 						// Default to true
16210 						if (format.remove === undef && format.selector && !format.inline)
16211 							format.remove = 'none';
16212 
16213 						// Mark format as a mixed format inline + block level
16214 						if (format.selector && format.inline) {
16215 							format.mixed = true;
16216 							format.block_expand = true;
16217 						}
16218 
16219 						// Split classes if needed
16220 						if (typeof(format.classes) === 'string')
16221 							format.classes = format.classes.split(/\s+/);
16222 					});
16223 
16224 					formats[name] = format;
16225 				}
16226 			}
16227 		};
16228 
16229 		var getTextDecoration = function(node) {
16230 			var decoration;
16231 
16232 			ed.dom.getParent(node, function(n) {
16233 				decoration = ed.dom.getStyle(n, 'text-decoration');
16234 				return decoration && decoration !== 'none';
16235 			});
16236 
16237 			return decoration;
16238 		};
16239 
16240 		var processUnderlineAndColor = function(node) {
16241 			var textDecoration;
16242 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
16243 				textDecoration = getTextDecoration(node.parentNode);
16244 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
16245 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
16246 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
16247 					ed.dom.setStyle(node, 'text-decoration', null);
16248 				}
16249 			}
16250 		};
16251 
16252 		function apply(name, vars, node) {
16253 			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
16254 
16255 			function setElementFormat(elm, fmt) {
16256 				fmt = fmt || format;
16257 
16258 				if (elm) {
16259 					if (fmt.onformat) {
16260 						fmt.onformat(elm, fmt, vars, node);
16261 					}
16262 
16263 					each(fmt.styles, function(value, name) {
16264 						dom.setStyle(elm, name, replaceVars(value, vars));
16265 					});
16266 
16267 					each(fmt.attributes, function(value, name) {
16268 						dom.setAttrib(elm, name, replaceVars(value, vars));
16269 					});
16270 
16271 					each(fmt.classes, function(value) {
16272 						value = replaceVars(value, vars);
16273 
16274 						if (!dom.hasClass(elm, value))
16275 							dom.addClass(elm, value);
16276 					});
16277 				}
16278 			};
16279 			function adjustSelectionToVisibleSelection() {
16280 				function findSelectionEnd(start, end) {
16281 					var walker = new TreeWalker(end);
16282 					for (node = walker.current(); node; node = walker.prev()) {
16283 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
16284 							return node;
16285 						}
16286 					}
16287 				};
16288 
16289 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
16290 				// as this isn't visible to the user.
16291 				var rng = ed.selection.getRng();
16292 				var start = rng.startContainer;
16293 				var end = rng.endContainer;
16294 
16295 				if (start != end && rng.endOffset === 0) {
16296 					var newEnd = findSelectionEnd(start, end);
16297 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
16298 
16299 					rng.setEnd(newEnd, endOffset);
16300 				}
16301 
16302 				return rng;
16303 			}
16304 			
16305 			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
16306 				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
16307 				
16308 				// find the index of the first child list.
16309 				each(node.childNodes, function(n, index) {
16310 					if (n.nodeName === "UL" || n.nodeName === "OL") {
16311 						listIndex = index;
16312 						list = n;
16313 						return false;
16314 					}
16315 				});
16316 				
16317 				// get the index of the bookmarks
16318 				each(node.childNodes, function(n, index) {
16319 					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
16320 						if (n.id == bookmark.id + "_start") {
16321 							startIndex = index;
16322 						} else if (n.id == bookmark.id + "_end") {
16323 							endIndex = index;
16324 						}
16325 					}
16326 				});
16327 				
16328 				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
16329 				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
16330 					each(tinymce.grep(node.childNodes), process);
16331 					return 0;
16332 				} else {
16333 					currentWrapElm = dom.clone(wrapElm, FALSE);
16334 
16335 					// create a list of the nodes on the same side of the list as the selection
16336 					each(tinymce.grep(node.childNodes), function(n, index) {
16337 						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
16338 							nodes.push(n); 
16339 							n.parentNode.removeChild(n);
16340 						}
16341 					});
16342 
16343 					// insert the wrapping element either before or after the list.
16344 					if (startIndex < listIndex) {
16345 						node.insertBefore(currentWrapElm, list);
16346 					} else if (startIndex > listIndex) {
16347 						node.insertBefore(currentWrapElm, list.nextSibling);
16348 					}
16349 					
16350 					// add the new nodes to the list.
16351 					newWrappers.push(currentWrapElm);
16352 
16353 					each(nodes, function(node) {
16354 						currentWrapElm.appendChild(node);
16355 					});
16356 
16357 					return currentWrapElm;
16358 				}
16359 			};
16360 
16361 			function applyRngStyle(rng, bookmark, node_specific) {
16362 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
16363 
16364 				// Setup wrapper element
16365 				wrapName = format.inline || format.block;
16366 				wrapElm = dom.create(wrapName);
16367 				setElementFormat(wrapElm);
16368 
16369 				rangeUtils.walk(rng, function(nodes) {
16370 					var currentWrapElm;
16371 
16372 					function process(node) {
16373 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
16374 
16375 						lastContentEditable = contentEditable;
16376 						nodeName = node.nodeName.toLowerCase();
16377 						parentName = node.parentNode.nodeName.toLowerCase();
16378 
16379 						// Node has a contentEditable value
16380 						if (node.nodeType === 1 && getContentEditable(node)) {
16381 							lastContentEditable = contentEditable;
16382 							contentEditable = getContentEditable(node) === "true";
16383 							hasContentEditableState = true; // We don't want to wrap the container only it's children
16384 						}
16385 
16386 						// Stop wrapping on br elements
16387 						if (isEq(nodeName, 'br')) {
16388 							currentWrapElm = 0;
16389 
16390 							// Remove any br elements when we wrap things
16391 							if (format.block)
16392 								dom.remove(node);
16393 
16394 							return;
16395 						}
16396 
16397 						// If node is wrapper type
16398 						if (format.wrapper && matchNode(node, name, vars)) {
16399 							currentWrapElm = 0;
16400 							return;
16401 						}
16402 
16403 						// Can we rename the block
16404 						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
16405 							node = dom.rename(node, wrapName);
16406 							setElementFormat(node);
16407 							newWrappers.push(node);
16408 							currentWrapElm = 0;
16409 							return;
16410 						}
16411 
16412 						// Handle selector patterns
16413 						if (format.selector) {
16414 							// Look for matching formats
16415 							each(formatList, function(format) {
16416 								// Check collapsed state if it exists
16417 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
16418 									return;
16419 								}
16420 
16421 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
16422 									setElementFormat(node, format);
16423 									found = true;
16424 								}
16425 							});
16426 
16427 							// Continue processing if a selector match wasn't found and a inline element is defined
16428 							if (!format.inline || found) {
16429 								currentWrapElm = 0;
16430 								return;
16431 							}
16432 						}
16433 
16434 						// Is it valid to wrap this item
16435 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
16436 								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
16437 							// Start wrapping
16438 							if (!currentWrapElm) {
16439 								// Wrap the node
16440 								currentWrapElm = dom.clone(wrapElm, FALSE);
16441 								node.parentNode.insertBefore(currentWrapElm, node);
16442 								newWrappers.push(currentWrapElm);
16443 							}
16444 
16445 							currentWrapElm.appendChild(node);
16446 						} else if (nodeName == 'li' && bookmark) {
16447 							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
16448 							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
16449 						} else {
16450 							// Start a new wrapper for possible children
16451 							currentWrapElm = 0;
16452 							
16453 							each(tinymce.grep(node.childNodes), process);
16454 
16455 							if (hasContentEditableState) {
16456 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16457 							}
16458 
16459 							// End the last wrapper
16460 							currentWrapElm = 0;
16461 						}
16462 					};
16463 
16464 					// Process siblings from range
16465 					each(nodes, process);
16466 				});
16467 
16468 				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
16469 				if (format.wrap_links === false) {
16470 					each(newWrappers, function(node) {
16471 						function process(node) {
16472 							var i, currentWrapElm, children;
16473 
16474 							if (node.nodeName === 'A') {
16475 								currentWrapElm = dom.clone(wrapElm, FALSE);
16476 								newWrappers.push(currentWrapElm);
16477 
16478 								children = tinymce.grep(node.childNodes);
16479 								for (i = 0; i < children.length; i++)
16480 									currentWrapElm.appendChild(children[i]);
16481 
16482 								node.appendChild(currentWrapElm);
16483 							}
16484 
16485 							each(tinymce.grep(node.childNodes), process);
16486 						};
16487 
16488 						process(node);
16489 					});
16490 				}
16491 
16492 				// Cleanup
16493 				
16494 				each(newWrappers, function(node) {
16495 					var childCount;
16496 
16497 					function getChildCount(node) {
16498 						var count = 0;
16499 
16500 						each(node.childNodes, function(node) {
16501 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
16502 								count++;
16503 						});
16504 
16505 						return count;
16506 					};
16507 
16508 					function mergeStyles(node) {
16509 						var child, clone;
16510 
16511 						each(node.childNodes, function(node) {
16512 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
16513 								child = node;
16514 								return FALSE; // break loop
16515 							}
16516 						});
16517 
16518 						// If child was found and of the same type as the current node
16519 						if (child && matchName(child, format)) {
16520 							clone = dom.clone(child, FALSE);
16521 							setElementFormat(clone);
16522 
16523 							dom.replace(clone, node, TRUE);
16524 							dom.remove(child, 1);
16525 						}
16526 
16527 						return clone || node;
16528 					};
16529 
16530 					childCount = getChildCount(node);
16531 
16532 					// Remove empty nodes but only if there is multiple wrappers and they are not block
16533 					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
16534 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
16535 						dom.remove(node, 1);
16536 						return;
16537 					}
16538 
16539 					if (format.inline || format.wrapper) {
16540 						// Merges the current node with it's children of similar type to reduce the number of elements
16541 						if (!format.exact && childCount === 1)
16542 							node = mergeStyles(node);
16543 
16544 						// Remove/merge children
16545 						each(formatList, function(format) {
16546 							// Merge all children of similar type will move styles from child to parent
16547 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
16548 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16549 							each(dom.select(format.inline, node), function(child) {
16550 								var parent;
16551 
16552 								// When wrap_links is set to false we don't want
16553 								// to remove the format on children within links
16554 								if (format.wrap_links === false) {
16555 									parent = child.parentNode;
16556 
16557 									do {
16558 										if (parent.nodeName === 'A')
16559 											return;
16560 									} while (parent = parent.parentNode);
16561 								}
16562 
16563 								removeFormat(format, vars, child, format.exact ? child : null);
16564 							});
16565 						});
16566 
16567 						// Remove child if direct parent is of same type
16568 						if (matchNode(node.parentNode, name, vars)) {
16569 							dom.remove(node, 1);
16570 							node = 0;
16571 							return TRUE;
16572 						}
16573 
16574 						// Look for parent with similar style format
16575 						if (format.merge_with_parents) {
16576 							dom.getParent(node.parentNode, function(parent) {
16577 								if (matchNode(parent, name, vars)) {
16578 									dom.remove(node, 1);
16579 									node = 0;
16580 									return TRUE;
16581 								}
16582 							});
16583 						}
16584 
16585 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16586 						if (node && format.merge_siblings !== false) {
16587 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16588 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16589 						}
16590 					}
16591 				});
16592 			};
16593 
16594 			if (format) {
16595 				if (node) {
16596 					if (node.nodeType) {
16597 						rng = dom.createRng();
16598 						rng.setStartBefore(node);
16599 						rng.setEndAfter(node);
16600 						applyRngStyle(expandRng(rng, formatList), null, true);
16601 					} else {
16602 						applyRngStyle(node, null, true);
16603 					}
16604 				} else {
16605 					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16606 						// Obtain selection node before selection is unselected by applyRngStyle()
16607 						var curSelNode = ed.selection.getNode();
16608 
16609 						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
16610 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
16611 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
16612 							apply(formatList[0].defaultBlock);
16613 						}
16614 
16615 						// Apply formatting to selection
16616 						ed.selection.setRng(adjustSelectionToVisibleSelection());
16617 						bookmark = selection.getBookmark();
16618 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
16619 
16620 						// Colored nodes should be underlined so that the color of the underline matches the text color.
16621 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
16622 							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
16623 							processUnderlineAndColor(curSelNode);
16624 						}
16625 
16626 						selection.moveToBookmark(bookmark);
16627 						moveStart(selection.getRng(TRUE));
16628 						ed.nodeChanged();
16629 					} else
16630 						performCaretAction('apply', name, vars);
16631 				}
16632 			}
16633 		};
16634 
16635 		function remove(name, vars, node) {
16636 			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
16637 
16638 			// Merges the styles for each node
16639 			function process(node) {
16640 				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
16641 
16642 				// Node has a contentEditable value
16643 				if (node.nodeType === 1 && getContentEditable(node)) {
16644 					lastContentEditable = contentEditable;
16645 					contentEditable = getContentEditable(node) === "true";
16646 					hasContentEditableState = true; // We don't want to wrap the container only it's children
16647 				}
16648 
16649 				// Grab the children first since the nodelist might be changed
16650 				children = tinymce.grep(node.childNodes);
16651 
16652 				// Process current node
16653 				if (contentEditable && !hasContentEditableState) {
16654 					for (i = 0, l = formatList.length; i < l; i++) {
16655 						if (removeFormat(formatList[i], vars, node, node))
16656 							break;
16657 					}
16658 				}
16659 
16660 				// Process the children
16661 				if (format.deep) {
16662 					if (children.length) {					
16663 						for (i = 0, l = children.length; i < l; i++)
16664 							process(children[i]);
16665 
16666 						if (hasContentEditableState) {
16667 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16668 						}
16669 					}
16670 				}
16671 			};
16672 
16673 			function findFormatRoot(container) {
16674 				var formatRoot;
16675 
16676 				// Find format root
16677 				each(getParents(container.parentNode).reverse(), function(parent) {
16678 					var format;
16679 
16680 					// Find format root element
16681 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
16682 						// Is the node matching the format we are looking for
16683 						format = matchNode(parent, name, vars);
16684 						if (format && format.split !== false)
16685 							formatRoot = parent;
16686 					}
16687 				});
16688 
16689 				return formatRoot;
16690 			};
16691 
16692 			function wrapAndSplit(format_root, container, target, split) {
16693 				var parent, clone, lastClone, firstClone, i, formatRootParent;
16694 
16695 				// Format root found then clone formats and split it
16696 				if (format_root) {
16697 					formatRootParent = format_root.parentNode;
16698 
16699 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
16700 						clone = dom.clone(parent, FALSE);
16701 
16702 						for (i = 0; i < formatList.length; i++) {
16703 							if (removeFormat(formatList[i], vars, clone, clone)) {
16704 								clone = 0;
16705 								break;
16706 							}
16707 						}
16708 
16709 						// Build wrapper node
16710 						if (clone) {
16711 							if (lastClone)
16712 								clone.appendChild(lastClone);
16713 
16714 							if (!firstClone)
16715 								firstClone = clone;
16716 
16717 							lastClone = clone;
16718 						}
16719 					}
16720 
16721 					// Never split block elements if the format is mixed
16722 					if (split && (!format.mixed || !isBlock(format_root)))
16723 						container = dom.split(format_root, container);
16724 
16725 					// Wrap container in cloned formats
16726 					if (lastClone) {
16727 						target.parentNode.insertBefore(lastClone, target);
16728 						firstClone.appendChild(target);
16729 					}
16730 				}
16731 
16732 				return container;
16733 			};
16734 
16735 			function splitToFormatRoot(container) {
16736 				return wrapAndSplit(findFormatRoot(container), container, container, true);
16737 			};
16738 
16739 			function unwrap(start) {
16740 				var node = dom.get(start ? '_start' : '_end'),
16741 					out = node[start ? 'firstChild' : 'lastChild'];
16742 
16743 				// If the end is placed within the start the result will be removed
16744 				// So this checks if the out node is a bookmark node if it is it
16745 				// checks for another more suitable node
16746 				if (isBookmarkNode(out))
16747 					out = out[start ? 'firstChild' : 'lastChild'];
16748 
16749 				dom.remove(node, true);
16750 
16751 				return out;
16752 			};
16753 
16754 			function removeRngStyle(rng) {
16755 				var startContainer, endContainer, node;
16756 
16757 				rng = expandRng(rng, formatList, TRUE);
16758 
16759 				if (format.split) {
16760 					startContainer = getContainer(rng, TRUE);
16761 					endContainer = getContainer(rng);
16762 
16763 					if (startContainer != endContainer) {
16764 						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
16765 						// This will happen if you tripple click a table cell and use remove formatting
16766 						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
16767 							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
16768 						}
16769 
16770 						// Wrap start/end nodes in span element since these might be cloned/moved
16771 						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
16772 						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
16773 
16774 						// Split start/end
16775 						splitToFormatRoot(startContainer);
16776 						splitToFormatRoot(endContainer);
16777 
16778 						// Unwrap start/end to get real elements again
16779 						startContainer = unwrap(TRUE);
16780 						endContainer = unwrap();
16781 					} else
16782 						startContainer = endContainer = splitToFormatRoot(startContainer);
16783 
16784 					// Update range positions since they might have changed after the split operations
16785 					rng.startContainer = startContainer.parentNode;
16786 					rng.startOffset = nodeIndex(startContainer);
16787 					rng.endContainer = endContainer.parentNode;
16788 					rng.endOffset = nodeIndex(endContainer) + 1;
16789 				}
16790 
16791 				// Remove items between start/end
16792 				rangeUtils.walk(rng, function(nodes) {
16793 					each(nodes, function(node) {
16794 						process(node);
16795 
16796 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
16797 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
16798 							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
16799 						}
16800 					});
16801 				});
16802 			};
16803 
16804 			// Handle node
16805 			if (node) {
16806 				if (node.nodeType) {
16807 					rng = dom.createRng();
16808 					rng.setStartBefore(node);
16809 					rng.setEndAfter(node);
16810 					removeRngStyle(rng);
16811 				} else {
16812 					removeRngStyle(node);
16813 				}
16814 
16815 				return;
16816 			}
16817 
16818 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16819 				bookmark = selection.getBookmark();
16820 				removeRngStyle(selection.getRng(TRUE));
16821 				selection.moveToBookmark(bookmark);
16822 
16823 				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
16824 				if (format.inline && match(name, vars, selection.getStart())) {
16825 					moveStart(selection.getRng(true));
16826 				}
16827 
16828 				ed.nodeChanged();
16829 			} else
16830 				performCaretAction('remove', name, vars);
16831 		};
16832 
16833 		function toggle(name, vars, node) {
16834 			var fmt = get(name);
16835 
16836 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
16837 				remove(name, vars, node);
16838 			else
16839 				apply(name, vars, node);
16840 		};
16841 
16842 		function matchNode(node, name, vars, similar) {
16843 			var formatList = get(name), format, i, classes;
16844 
16845 			function matchItems(node, format, item_name) {
16846 				var key, value, items = format[item_name], i;
16847 
16848 				// Custom match
16849 				if (format.onmatch) {
16850 					return format.onmatch(node, format, item_name);
16851 				}
16852 
16853 				// Check all items
16854 				if (items) {
16855 					// Non indexed object
16856 					if (items.length === undef) {
16857 						for (key in items) {
16858 							if (items.hasOwnProperty(key)) {
16859 								if (item_name === 'attributes')
16860 									value = dom.getAttrib(node, key);
16861 								else
16862 									value = getStyle(node, key);
16863 
16864 								if (similar && !value && !format.exact)
16865 									return;
16866 
16867 								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
16868 									return;
16869 							}
16870 						}
16871 					} else {
16872 						// Only one match needed for indexed arrays
16873 						for (i = 0; i < items.length; i++) {
16874 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
16875 								return format;
16876 						}
16877 					}
16878 				}
16879 
16880 				return format;
16881 			};
16882 
16883 			if (formatList && node) {
16884 				// Check each format in list
16885 				for (i = 0; i < formatList.length; i++) {
16886 					format = formatList[i];
16887 
16888 					// Name name, attributes, styles and classes
16889 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
16890 						// Match classes
16891 						if (classes = format.classes) {
16892 							for (i = 0; i < classes.length; i++) {
16893 								if (!dom.hasClass(node, classes[i]))
16894 									return;
16895 							}
16896 						}
16897 
16898 						return format;
16899 					}
16900 				}
16901 			}
16902 		};
16903 
16904 		function match(name, vars, node) {
16905 			var startNode;
16906 
16907 			function matchParents(node) {
16908 				// Find first node with similar format settings
16909 				node = dom.getParent(node, function(node) {
16910 					return !!matchNode(node, name, vars, true);
16911 				});
16912 
16913 				// Do an exact check on the similar format element
16914 				return matchNode(node, name, vars);
16915 			};
16916 
16917 			// Check specified node
16918 			if (node)
16919 				return matchParents(node);
16920 
16921 			// Check selected node
16922 			node = selection.getNode();
16923 			if (matchParents(node))
16924 				return TRUE;
16925 
16926 			// Check start node if it's different
16927 			startNode = selection.getStart();
16928 			if (startNode != node) {
16929 				if (matchParents(startNode))
16930 					return TRUE;
16931 			}
16932 
16933 			return FALSE;
16934 		};
16935 
16936 		function matchAll(names, vars) {
16937 			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
16938 
16939 			// Check start of selection for formats
16940 			startElement = selection.getStart();
16941 			dom.getParent(startElement, function(node) {
16942 				var i, name;
16943 
16944 				for (i = 0; i < names.length; i++) {
16945 					name = names[i];
16946 
16947 					if (!checkedMap[name] && matchNode(node, name, vars)) {
16948 						checkedMap[name] = true;
16949 						matchedFormatNames.push(name);
16950 					}
16951 				}
16952 			}, dom.getRoot());
16953 
16954 			return matchedFormatNames;
16955 		};
16956 
16957 		function canApply(name) {
16958 			var formatList = get(name), startNode, parents, i, x, selector;
16959 
16960 			if (formatList) {
16961 				startNode = selection.getStart();
16962 				parents = getParents(startNode);
16963 
16964 				for (x = formatList.length - 1; x >= 0; x--) {
16965 					selector = formatList[x].selector;
16966 
16967 					// Format is not selector based, then always return TRUE
16968 					if (!selector)
16969 						return TRUE;
16970 
16971 					for (i = parents.length - 1; i >= 0; i--) {
16972 						if (dom.is(parents[i], selector))
16973 							return TRUE;
16974 					}
16975 				}
16976 			}
16977 
16978 			return FALSE;
16979 		};
16980 
16981 		function formatChanged(formats, callback) {
16982 			var currentFormats;
16983 
16984 			// Setup format node change logic
16985 			if (!formatChangeData) {
16986 				formatChangeData = {};
16987 				currentFormats = {};
16988 
16989 				ed.onNodeChange.addToTop(function(ed, cm, node) {
16990 					var parents = getParents(node), matchedFormats = {};
16991 
16992 					// Check for new formats
16993 					each(formatChangeData, function(callbacks, format) {
16994 						each(parents, function(node) {
16995 							if (matchNode(node, format, {}, true)) {
16996 								if (!currentFormats[format]) {
16997 									// Execute callbacks
16998 									each(callbacks, function(callback) {
16999 										callback(true, {node: node, format: format, parents: parents});
17000 									});
17001 
17002 									currentFormats[format] = callbacks;
17003 								}
17004 
17005 								matchedFormats[format] = callbacks;
17006 								return false;
17007 							}
17008 						});
17009 					});
17010 
17011 					// Check if current formats still match
17012 					each(currentFormats, function(callbacks, format) {
17013 						if (!matchedFormats[format]) {
17014 							delete currentFormats[format];
17015 
17016 							each(callbacks, function(callback) {
17017 								callback(false, {node: node, format: format, parents: parents});
17018 							});
17019 						}
17020 					});
17021 				});
17022 			}
17023 
17024 			// Add format listeners
17025 			each(formats.split(','), function(format) {
17026 				if (!formatChangeData[format]) {
17027 					formatChangeData[format] = [];
17028 				}
17029 
17030 				formatChangeData[format].push(callback);
17031 			});
17032 
17033 			return this;
17034 		};
17035 
17036 		// Expose to public
17037 		tinymce.extend(this, {
17038 			get : get,
17039 			register : register,
17040 			apply : apply,
17041 			remove : remove,
17042 			toggle : toggle,
17043 			match : match,
17044 			matchAll : matchAll,
17045 			matchNode : matchNode,
17046 			canApply : canApply,
17047 			formatChanged: formatChanged
17048 		});
17049 
17050 		// Initialize
17051 		defaultFormats();
17052 		addKeyboardShortcuts();
17053 
17054 		// Private functions
17055 
17056 		function matchName(node, format) {
17057 			// Check for inline match
17058 			if (isEq(node, format.inline))
17059 				return TRUE;
17060 
17061 			// Check for block match
17062 			if (isEq(node, format.block))
17063 				return TRUE;
17064 
17065 			// Check for selector match
17066 			if (format.selector)
17067 				return dom.is(node, format.selector);
17068 		};
17069 
17070 		function isEq(str1, str2) {
17071 			str1 = str1 || '';
17072 			str2 = str2 || '';
17073 
17074 			str1 = '' + (str1.nodeName || str1);
17075 			str2 = '' + (str2.nodeName || str2);
17076 
17077 			return str1.toLowerCase() == str2.toLowerCase();
17078 		};
17079 
17080 		function getStyle(node, name) {
17081 			var styleVal = dom.getStyle(node, name);
17082 
17083 			// Force the format to hex
17084 			if (name == 'color' || name == 'backgroundColor')
17085 				styleVal = dom.toHex(styleVal);
17086 
17087 			// Opera will return bold as 700
17088 			if (name == 'fontWeight' && styleVal == 700)
17089 				styleVal = 'bold';
17090 
17091 			return '' + styleVal;
17092 		};
17093 
17094 		function replaceVars(value, vars) {
17095 			if (typeof(value) != "string")
17096 				value = value(vars);
17097 			else if (vars) {
17098 				value = value.replace(/%(\w+)/g, function(str, name) {
17099 					return vars[name] || str;
17100 				});
17101 			}
17102 
17103 			return value;
17104 		};
17105 
17106 		function isWhiteSpaceNode(node) {
17107 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
17108 		};
17109 
17110 		function wrap(node, name, attrs) {
17111 			var wrapper = dom.create(name, attrs);
17112 
17113 			node.parentNode.insertBefore(wrapper, node);
17114 			wrapper.appendChild(node);
17115 
17116 			return wrapper;
17117 		};
17118 
17119 		function expandRng(rng, format, remove) {
17120 			var sibling, lastIdx, leaf, endPoint,
17121 				startContainer = rng.startContainer,
17122 				startOffset = rng.startOffset,
17123 				endContainer = rng.endContainer,
17124 				endOffset = rng.endOffset;
17125 
17126 			// This function walks up the tree if there is no siblings before/after the node
17127 			function findParentContainer(start) {
17128 				var container, parent, child, sibling, siblingName, root;
17129 
17130 				container = parent = start ? startContainer : endContainer;
17131 				siblingName = start ? 'previousSibling' : 'nextSibling';
17132 				root = dom.getRoot();
17133 
17134 				// If it's a text node and the offset is inside the text
17135 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
17136 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
17137 						return container;
17138 					}
17139 				}
17140 
17141 				for (;;) {
17142 					// Stop expanding on block elements
17143 					if (!format[0].block_expand && isBlock(parent))
17144 						return parent;
17145 
17146 					// Walk left/right
17147 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
17148 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
17149 							return parent;
17150 						}
17151 					}
17152 
17153 					// Check if we can move up are we at root level or body level
17154 					if (parent.parentNode == root) {
17155 						container = parent;
17156 						break;
17157 					}
17158 
17159 					parent = parent.parentNode;
17160 				}
17161 
17162 				return container;
17163 			};
17164 
17165 			// This function walks down the tree to find the leaf at the selection.
17166 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
17167 			function findLeaf(node, offset) {
17168 				if (offset === undef)
17169 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17170 				while (node && node.hasChildNodes()) {
17171 					node = node.childNodes[offset];
17172 					if (node)
17173 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17174 				}
17175 				return { node: node, offset: offset };
17176 			}
17177 
17178 			// If index based start position then resolve it
17179 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
17180 				lastIdx = startContainer.childNodes.length - 1;
17181 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
17182 
17183 				if (startContainer.nodeType == 3)
17184 					startOffset = 0;
17185 			}
17186 
17187 			// If index based end position then resolve it
17188 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
17189 				lastIdx = endContainer.childNodes.length - 1;
17190 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
17191 
17192 				if (endContainer.nodeType == 3)
17193 					endOffset = endContainer.nodeValue.length;
17194 			}
17195 
17196 			// Expands the node to the closes contentEditable false element if it exists
17197 			function findParentContentEditable(node) {
17198 				var parent = node;
17199 
17200 				while (parent) {
17201 					if (parent.nodeType === 1 && getContentEditable(parent)) {
17202 						return getContentEditable(parent) === "false" ? parent : node;
17203 					}
17204 
17205 					parent = parent.parentNode;
17206 				}
17207 
17208 				return node;
17209 			};
17210 
17211 			function findWordEndPoint(container, offset, start) {
17212 				var walker, node, pos, lastTextNode;
17213 
17214 				function findSpace(node, offset) {
17215 					var pos, pos2, str = node.nodeValue;
17216 
17217 					if (typeof(offset) == "undefined") {
17218 						offset = start ? str.length : 0;
17219 					}
17220 
17221 					if (start) {
17222 						pos = str.lastIndexOf(' ', offset);
17223 						pos2 = str.lastIndexOf('\u00a0', offset);
17224 						pos = pos > pos2 ? pos : pos2;
17225 
17226 						// Include the space on remove to avoid tag soup
17227 						if (pos !== -1 && !remove) {
17228 							pos++;
17229 						}
17230 					} else {
17231 						pos = str.indexOf(' ', offset);
17232 						pos2 = str.indexOf('\u00a0', offset);
17233 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
17234 					}
17235 
17236 					return pos;
17237 				};
17238 
17239 				if (container.nodeType === 3) {
17240 					pos = findSpace(container, offset);
17241 
17242 					if (pos !== -1) {
17243 						return {container : container, offset : pos};
17244 					}
17245 
17246 					lastTextNode = container;
17247 				}
17248 
17249 				// Walk the nodes inside the block
17250 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
17251 				while (node = walker[start ? 'prev' : 'next']()) {
17252 					if (node.nodeType === 3) {
17253 						lastTextNode = node;
17254 						pos = findSpace(node);
17255 
17256 						if (pos !== -1) {
17257 							return {container : node, offset : pos};
17258 						}
17259 					} else if (isBlock(node)) {
17260 						break;
17261 					}
17262 				}
17263 
17264 				if (lastTextNode) {
17265 					if (start) {
17266 						offset = 0;
17267 					} else {
17268 						offset = lastTextNode.length;
17269 					}
17270 
17271 					return {container: lastTextNode, offset: offset};
17272 				}
17273 			};
17274 
17275 			function findSelectorEndPoint(container, sibling_name) {
17276 				var parents, i, y, curFormat;
17277 
17278 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
17279 					container = container[sibling_name];
17280 
17281 				parents = getParents(container);
17282 				for (i = 0; i < parents.length; i++) {
17283 					for (y = 0; y < format.length; y++) {
17284 						curFormat = format[y];
17285 
17286 						// If collapsed state is set then skip formats that doesn't match that
17287 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
17288 							continue;
17289 
17290 						if (dom.is(parents[i], curFormat.selector))
17291 							return parents[i];
17292 					}
17293 				}
17294 
17295 				return container;
17296 			};
17297 
17298 			function findBlockEndPoint(container, sibling_name, sibling_name2) {
17299 				var node;
17300 
17301 				// Expand to block of similar type
17302 				if (!format[0].wrapper)
17303 					node = dom.getParent(container, format[0].block);
17304 
17305 				// Expand to first wrappable block element or any block element
17306 				if (!node)
17307 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
17308 
17309 				// Exclude inner lists from wrapping
17310 				if (node && format[0].wrapper)
17311 					node = getParents(node, 'ul,ol').reverse()[0] || node;
17312 
17313 				// Didn't find a block element look for first/last wrappable element
17314 				if (!node) {
17315 					node = container;
17316 
17317 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
17318 						node = node[sibling_name];
17319 
17320 						// Break on BR but include it will be removed later on
17321 						// we can't remove it now since we need to check if it can be wrapped
17322 						if (isEq(node, 'br'))
17323 							break;
17324 					}
17325 				}
17326 
17327 				return node || container;
17328 			};
17329 
17330 			// Expand to closest contentEditable element
17331 			startContainer = findParentContentEditable(startContainer);
17332 			endContainer = findParentContentEditable(endContainer);
17333 
17334 			// Exclude bookmark nodes if possible
17335 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
17336 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
17337 				startContainer = startContainer.nextSibling || startContainer;
17338 
17339 				if (startContainer.nodeType == 3)
17340 					startOffset = 0;
17341 			}
17342 
17343 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
17344 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
17345 				endContainer = endContainer.previousSibling || endContainer;
17346 
17347 				if (endContainer.nodeType == 3)
17348 					endOffset = endContainer.length;
17349 			}
17350 
17351 			if (format[0].inline) {
17352 				if (rng.collapsed) {
17353 					// Expand left to closest word boundery
17354 					endPoint = findWordEndPoint(startContainer, startOffset, true);
17355 					if (endPoint) {
17356 						startContainer = endPoint.container;
17357 						startOffset = endPoint.offset;
17358 					}
17359 
17360 					// Expand right to closest word boundery
17361 					endPoint = findWordEndPoint(endContainer, endOffset);
17362 					if (endPoint) {
17363 						endContainer = endPoint.container;
17364 						endOffset = endPoint.offset;
17365 					}
17366 				}
17367 
17368 				// Avoid applying formatting to a trailing space.
17369 				leaf = findLeaf(endContainer, endOffset);
17370 				if (leaf.node) {
17371 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
17372 						leaf = findLeaf(leaf.node.previousSibling);
17373 
17374 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
17375 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
17376 
17377 						if (leaf.offset > 1) {
17378 							endContainer = leaf.node;
17379 							endContainer.splitText(leaf.offset - 1);
17380 						}
17381 					}
17382 				}
17383 			}
17384 
17385 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
17386 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
17387 			// This will reduce the number of wrapper elements that needs to be created
17388 			// Move start point up the tree
17389 			if (format[0].inline || format[0].block_expand) {
17390 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
17391 					startContainer = findParentContainer(true);
17392 				}
17393 
17394 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
17395 					endContainer = findParentContainer();
17396 				}
17397 			}
17398 
17399 			// Expand start/end container to matching selector
17400 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
17401 				// Find new startContainer/endContainer if there is better one
17402 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
17403 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
17404 			}
17405 
17406 			// Expand start/end container to matching block element or text node
17407 			if (format[0].block || format[0].selector) {
17408 				// Find new startContainer/endContainer if there is better one
17409 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
17410 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
17411 
17412 				// Non block element then try to expand up the leaf
17413 				if (format[0].block) {
17414 					if (!isBlock(startContainer))
17415 						startContainer = findParentContainer(true);
17416 
17417 					if (!isBlock(endContainer))
17418 						endContainer = findParentContainer();
17419 				}
17420 			}
17421 
17422 			// Setup index for startContainer
17423 			if (startContainer.nodeType == 1) {
17424 				startOffset = nodeIndex(startContainer);
17425 				startContainer = startContainer.parentNode;
17426 			}
17427 
17428 			// Setup index for endContainer
17429 			if (endContainer.nodeType == 1) {
17430 				endOffset = nodeIndex(endContainer) + 1;
17431 				endContainer = endContainer.parentNode;
17432 			}
17433 
17434 			// Return new range like object
17435 			return {
17436 				startContainer : startContainer,
17437 				startOffset : startOffset,
17438 				endContainer : endContainer,
17439 				endOffset : endOffset
17440 			};
17441 		}
17442 
17443 		function removeFormat(format, vars, node, compare_node) {
17444 			var i, attrs, stylesModified;
17445 
17446 			// Check if node matches format
17447 			if (!matchName(node, format))
17448 				return FALSE;
17449 
17450 			// Should we compare with format attribs and styles
17451 			if (format.remove != 'all') {
17452 				// Remove styles
17453 				each(format.styles, function(value, name) {
17454 					value = replaceVars(value, vars);
17455 
17456 					// Indexed array
17457 					if (typeof(name) === 'number') {
17458 						name = value;
17459 						compare_node = 0;
17460 					}
17461 
17462 					if (!compare_node || isEq(getStyle(compare_node, name), value))
17463 						dom.setStyle(node, name, '');
17464 
17465 					stylesModified = 1;
17466 				});
17467 
17468 				// Remove style attribute if it's empty
17469 				if (stylesModified && dom.getAttrib(node, 'style') == '') {
17470 					node.removeAttribute('style');
17471 					node.removeAttribute('data-mce-style');
17472 				}
17473 
17474 				// Remove attributes
17475 				each(format.attributes, function(value, name) {
17476 					var valueOut;
17477 
17478 					value = replaceVars(value, vars);
17479 
17480 					// Indexed array
17481 					if (typeof(name) === 'number') {
17482 						name = value;
17483 						compare_node = 0;
17484 					}
17485 
17486 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
17487 						// Keep internal classes
17488 						if (name == 'class') {
17489 							value = dom.getAttrib(node, name);
17490 							if (value) {
17491 								// Build new class value where everything is removed except the internal prefixed classes
17492 								valueOut = '';
17493 								each(value.split(/\s+/), function(cls) {
17494 									if (/mce\w+/.test(cls))
17495 										valueOut += (valueOut ? ' ' : '') + cls;
17496 								});
17497 
17498 								// We got some internal classes left
17499 								if (valueOut) {
17500 									dom.setAttrib(node, name, valueOut);
17501 									return;
17502 								}
17503 							}
17504 						}
17505 
17506 						// IE6 has a bug where the attribute doesn't get removed correctly
17507 						if (name == "class")
17508 							node.removeAttribute('className');
17509 
17510 						// Remove mce prefixed attributes
17511 						if (MCE_ATTR_RE.test(name))
17512 							node.removeAttribute('data-mce-' + name);
17513 
17514 						node.removeAttribute(name);
17515 					}
17516 				});
17517 
17518 				// Remove classes
17519 				each(format.classes, function(value) {
17520 					value = replaceVars(value, vars);
17521 
17522 					if (!compare_node || dom.hasClass(compare_node, value))
17523 						dom.removeClass(node, value);
17524 				});
17525 
17526 				// Check for non internal attributes
17527 				attrs = dom.getAttribs(node);
17528 				for (i = 0; i < attrs.length; i++) {
17529 					if (attrs[i].nodeName.indexOf('_') !== 0)
17530 						return FALSE;
17531 				}
17532 			}
17533 
17534 			// Remove the inline child if it's empty for example <b> or <span>
17535 			if (format.remove != 'none') {
17536 				removeNode(node, format);
17537 				return TRUE;
17538 			}
17539 		};
17540 
17541 		function removeNode(node, format) {
17542 			var parentNode = node.parentNode, rootBlockElm;
17543 
17544 			function find(node, next, inc) {
17545 				node = getNonWhiteSpaceSibling(node, next, inc);
17546 
17547 				return !node || (node.nodeName == 'BR' || isBlock(node));
17548 			};
17549 
17550 			if (format.block) {
17551 				if (!forcedRootBlock) {
17552 					// Append BR elements if needed before we remove the block
17553 					if (isBlock(node) && !isBlock(parentNode)) {
17554 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
17555 							node.insertBefore(dom.create('br'), node.firstChild);
17556 
17557 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
17558 							node.appendChild(dom.create('br'));
17559 					}
17560 				} else {
17561 					// Wrap the block in a forcedRootBlock if we are at the root of document
17562 					if (parentNode == dom.getRoot()) {
17563 						if (!format.list_block || !isEq(node, format.list_block)) {
17564 							each(tinymce.grep(node.childNodes), function(node) {
17565 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
17566 									if (!rootBlockElm)
17567 										rootBlockElm = wrap(node, forcedRootBlock);
17568 									else
17569 										rootBlockElm.appendChild(node);
17570 								} else
17571 									rootBlockElm = 0;
17572 							});
17573 						}
17574 					}
17575 				}
17576 			}
17577 
17578 			// Never remove nodes that isn't the specified inline element if a selector is specified too
17579 			if (format.selector && format.inline && !isEq(format.inline, node))
17580 				return;
17581 
17582 			dom.remove(node, 1);
17583 		};
17584 
17585 		function getNonWhiteSpaceSibling(node, next, inc) {
17586 			if (node) {
17587 				next = next ? 'nextSibling' : 'previousSibling';
17588 
17589 				for (node = inc ? node : node[next]; node; node = node[next]) {
17590 					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
17591 						return node;
17592 				}
17593 			}
17594 		};
17595 
17596 		function isBookmarkNode(node) {
17597 			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
17598 		};
17599 
17600 		function mergeSiblings(prev, next) {
17601 			var marker, sibling, tmpSibling;
17602 
17603 			function compareElements(node1, node2) {
17604 				// Not the same name
17605 				if (node1.nodeName != node2.nodeName)
17606 					return FALSE;
17607 
17608 				function getAttribs(node) {
17609 					var attribs = {};
17610 
17611 					each(dom.getAttribs(node), function(attr) {
17612 						var name = attr.nodeName.toLowerCase();
17613 
17614 						// Don't compare internal attributes or style
17615 						if (name.indexOf('_') !== 0 && name !== 'style')
17616 							attribs[name] = dom.getAttrib(node, name);
17617 					});
17618 
17619 					return attribs;
17620 				};
17621 
17622 				function compareObjects(obj1, obj2) {
17623 					var value, name;
17624 
17625 					for (name in obj1) {
17626 						// Obj1 has item obj2 doesn't have
17627 						if (obj1.hasOwnProperty(name)) {
17628 							value = obj2[name];
17629 
17630 							// Obj2 doesn't have obj1 item
17631 							if (value === undef)
17632 								return FALSE;
17633 
17634 							// Obj2 item has a different value
17635 							if (obj1[name] != value)
17636 								return FALSE;
17637 
17638 							// Delete similar value
17639 							delete obj2[name];
17640 						}
17641 					}
17642 
17643 					// Check if obj 2 has something obj 1 doesn't have
17644 					for (name in obj2) {
17645 						// Obj2 has item obj1 doesn't have
17646 						if (obj2.hasOwnProperty(name))
17647 							return FALSE;
17648 					}
17649 
17650 					return TRUE;
17651 				};
17652 
17653 				// Attribs are not the same
17654 				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
17655 					return FALSE;
17656 
17657 				// Styles are not the same
17658 				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
17659 					return FALSE;
17660 
17661 				return TRUE;
17662 			};
17663 
17664 			function findElementSibling(node, sibling_name) {
17665 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
17666 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
17667 						return node;
17668 
17669 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
17670 						return sibling;
17671 				}
17672 
17673 				return node;
17674 			};
17675 
17676 			// Check if next/prev exists and that they are elements
17677 			if (prev && next) {
17678 				// If previous sibling is empty then jump over it
17679 				prev = findElementSibling(prev, 'previousSibling');
17680 				next = findElementSibling(next, 'nextSibling');
17681 
17682 				// Compare next and previous nodes
17683 				if (compareElements(prev, next)) {
17684 					// Append nodes between
17685 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
17686 						tmpSibling = sibling;
17687 						sibling = sibling.nextSibling;
17688 						prev.appendChild(tmpSibling);
17689 					}
17690 
17691 					// Remove next node
17692 					dom.remove(next);
17693 
17694 					// Move children into prev node
17695 					each(tinymce.grep(next.childNodes), function(node) {
17696 						prev.appendChild(node);
17697 					});
17698 
17699 					return prev;
17700 				}
17701 			}
17702 
17703 			return next;
17704 		};
17705 
17706 		function isTextBlock(name) {
17707 			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
17708 		};
17709 
17710 		function getContainer(rng, start) {
17711 			var container, offset, lastIdx, walker;
17712 
17713 			container = rng[start ? 'startContainer' : 'endContainer'];
17714 			offset = rng[start ? 'startOffset' : 'endOffset'];
17715 
17716 			if (container.nodeType == 1) {
17717 				lastIdx = container.childNodes.length - 1;
17718 
17719 				if (!start && offset)
17720 					offset--;
17721 
17722 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
17723 			}
17724 
17725 			// If start text node is excluded then walk to the next node
17726 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
17727 				container = new TreeWalker(container, ed.getBody()).next() || container;
17728 			}
17729 
17730 			// If end text node is excluded then walk to the previous node
17731 			if (container.nodeType === 3 && !start && offset === 0) {
17732 				container = new TreeWalker(container, ed.getBody()).prev() || container;
17733 			}
17734 
17735 			return container;
17736 		};
17737 
17738 		function performCaretAction(type, name, vars) {
17739 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
17740 
17741 			// Creates a caret container bogus element
17742 			function createCaretContainer(fill) {
17743 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
17744 
17745 				if (fill) {
17746 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
17747 				}
17748 
17749 				return caretContainer;
17750 			};
17751 
17752 			function isCaretContainerEmpty(node, nodes) {
17753 				while (node) {
17754 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
17755 						return false;
17756 					}
17757 
17758 					// Collect nodes
17759 					if (nodes && node.nodeType === 1) {
17760 						nodes.push(node);
17761 					}
17762 
17763 					node = node.firstChild;
17764 				}
17765 
17766 				return true;
17767 			};
17768 			
17769 			// Returns any parent caret container element
17770 			function getParentCaretContainer(node) {
17771 				while (node) {
17772 					if (node.id === caretContainerId) {
17773 						return node;
17774 					}
17775 
17776 					node = node.parentNode;
17777 				}
17778 			};
17779 
17780 			// Finds the first text node in the specified node
17781 			function findFirstTextNode(node) {
17782 				var walker;
17783 
17784 				if (node) {
17785 					walker = new TreeWalker(node, node);
17786 
17787 					for (node = walker.current(); node; node = walker.next()) {
17788 						if (node.nodeType === 3) {
17789 							return node;
17790 						}
17791 					}
17792 				}
17793 			};
17794 
17795 			// Removes the caret container for the specified node or all on the current document
17796 			function removeCaretContainer(node, move_caret) {
17797 				var child, rng;
17798 
17799 				if (!node) {
17800 					node = getParentCaretContainer(selection.getStart());
17801 
17802 					if (!node) {
17803 						while (node = dom.get(caretContainerId)) {
17804 							removeCaretContainer(node, false);
17805 						}
17806 					}
17807 				} else {
17808 					rng = selection.getRng(true);
17809 
17810 					if (isCaretContainerEmpty(node)) {
17811 						if (move_caret !== false) {
17812 							rng.setStartBefore(node);
17813 							rng.setEndBefore(node);
17814 						}
17815 
17816 						dom.remove(node);
17817 					} else {
17818 						child = findFirstTextNode(node);
17819 
17820 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
17821 							child = child.deleteData(0, 1);
17822 						}
17823 
17824 						dom.remove(node, 1);
17825 					}
17826 
17827 					selection.setRng(rng);
17828 				}
17829 			};
17830 			
17831 			// Applies formatting to the caret postion
17832 			function applyCaretFormat() {
17833 				var rng, caretContainer, textNode, offset, bookmark, container, text;
17834 
17835 				rng = selection.getRng(true);
17836 				offset = rng.startOffset;
17837 				container = rng.startContainer;
17838 				text = container.nodeValue;
17839 
17840 				caretContainer = getParentCaretContainer(selection.getStart());
17841 				if (caretContainer) {
17842 					textNode = findFirstTextNode(caretContainer);
17843 				}
17844 
17845 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
17846 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
17847 					// Get bookmark of caret position
17848 					bookmark = selection.getBookmark();
17849 
17850 					// Collapse bookmark range (WebKit)
17851 					rng.collapse(true);
17852 
17853 					// Expand the range to the closest word and split it at those points
17854 					rng = expandRng(rng, get(name));
17855 					rng = rangeUtils.split(rng);
17856 
17857 					// Apply the format to the range
17858 					apply(name, vars, rng);
17859 
17860 					// Move selection back to caret position
17861 					selection.moveToBookmark(bookmark);
17862 				} else {
17863 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
17864 						caretContainer = createCaretContainer(true);
17865 						textNode = caretContainer.firstChild;
17866 
17867 						rng.insertNode(caretContainer);
17868 						offset = 1;
17869 
17870 						apply(name, vars, caretContainer);
17871 					} else {
17872 						apply(name, vars, caretContainer);
17873 					}
17874 
17875 					// Move selection to text node
17876 					selection.setCursorLocation(textNode, offset);
17877 				}
17878 			};
17879 
17880 			function removeCaretFormat() {
17881 				var rng = selection.getRng(true), container, offset, bookmark,
17882 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
17883 
17884 				container = rng.startContainer;
17885 				offset = rng.startOffset;
17886 				node = container;
17887 
17888 				if (container.nodeType == 3) {
17889 					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
17890 						hasContentAfter = true;
17891 					}
17892 
17893 					node = node.parentNode;
17894 				}
17895 
17896 				while (node) {
17897 					if (matchNode(node, name, vars)) {
17898 						formatNode = node;
17899 						break;
17900 					}
17901 
17902 					if (node.nextSibling) {
17903 						hasContentAfter = true;
17904 					}
17905 
17906 					parents.push(node);
17907 					node = node.parentNode;
17908 				}
17909 
17910 				// Node doesn't have the specified format
17911 				if (!formatNode) {
17912 					return;
17913 				}
17914 
17915 				// Is there contents after the caret then remove the format on the element
17916 				if (hasContentAfter) {
17917 					// Get bookmark of caret position
17918 					bookmark = selection.getBookmark();
17919 
17920 					// Collapse bookmark range (WebKit)
17921 					rng.collapse(true);
17922 
17923 					// Expand the range to the closest word and split it at those points
17924 					rng = expandRng(rng, get(name), true);
17925 					rng = rangeUtils.split(rng);
17926 
17927 					// Remove the format from the range
17928 					remove(name, vars, rng);
17929 
17930 					// Move selection back to caret position
17931 					selection.moveToBookmark(bookmark);
17932 				} else {
17933 					caretContainer = createCaretContainer();
17934 
17935 					node = caretContainer;
17936 					for (i = parents.length - 1; i >= 0; i--) {
17937 						node.appendChild(dom.clone(parents[i], false));
17938 						node = node.firstChild;
17939 					}
17940 
17941 					// Insert invisible character into inner most format element
17942 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
17943 					node = node.firstChild;
17944 
17945 					// Insert caret container after the formated node
17946 					dom.insertAfter(caretContainer, formatNode);
17947 
17948 					// Move selection to text node
17949 					selection.setCursorLocation(node, 1);
17950 				}
17951 			};
17952 
17953 			// Checks if the parent caret container node isn't empty if that is the case it
17954 			// will remove the bogus state on all children that isn't empty
17955 			function unmarkBogusCaretParents() {
17956 				var i, caretContainer, node;
17957 
17958 				caretContainer = getParentCaretContainer(selection.getStart());
17959 				if (caretContainer && !dom.isEmpty(caretContainer)) {
17960 					tinymce.walk(caretContainer, function(node) {
17961 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
17962 							dom.setAttrib(node, 'data-mce-bogus', null);
17963 						}
17964 					}, 'childNodes');
17965 				}
17966 			};
17967 
17968 			// Only bind the caret events once
17969 			if (!self._hasCaretEvents) {
17970 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
17971 				ed.onBeforeGetContent.addToTop(function() {
17972 					var nodes = [], i;
17973 
17974 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
17975 						// Mark children
17976 						i = nodes.length;
17977 						while (i--) {
17978 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
17979 						}
17980 					}
17981 				});
17982 
17983 				// Remove caret container on mouse up and on key up
17984 				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
17985 					ed[name].addToTop(function() {
17986 						removeCaretContainer();
17987 						unmarkBogusCaretParents();
17988 					});
17989 				});
17990 
17991 				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
17992 				ed.onKeyDown.addToTop(function(ed, e) {
17993 					var keyCode = e.keyCode;
17994 
17995 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
17996 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
17997 					}
17998 
17999 					unmarkBogusCaretParents();
18000 				});
18001 
18002 				// Remove bogus state if they got filled by contents using editor.selection.setContent
18003 				selection.onSetContent.add(unmarkBogusCaretParents);
18004 
18005 				self._hasCaretEvents = true;
18006 			}
18007 
18008 			// Do apply or remove caret format
18009 			if (type == "apply") {
18010 				applyCaretFormat();
18011 			} else {
18012 				removeCaretFormat();
18013 			}
18014 		};
18015 
18016 		function moveStart(rng) {
18017 			var container = rng.startContainer,
18018 					offset = rng.startOffset, isAtEndOfText,
18019 					walker, node, nodes, tmpNode;
18020 
18021 			// Convert text node into index if possible
18022 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
18023 				// Get the parent container location and walk from there
18024 				offset = nodeIndex(container);
18025 				container = container.parentNode;
18026 				isAtEndOfText = true;
18027 			}
18028 
18029 			// Move startContainer/startOffset in to a suitable node
18030 			if (container.nodeType == 1) {
18031 				nodes = container.childNodes;
18032 				container = nodes[Math.min(offset, nodes.length - 1)];
18033 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
18034 
18035 				// If offset is at end of the parent node walk to the next one
18036 				if (offset > nodes.length - 1 || isAtEndOfText)
18037 					walker.next();
18038 
18039 				for (node = walker.current(); node; node = walker.next()) {
18040 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
18041 						// IE has a "neat" feature where it moves the start node into the closest element
18042 						// we can avoid this by inserting an element before it and then remove it after we set the selection
18043 						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
18044 						node.parentNode.insertBefore(tmpNode, node);
18045 
18046 						// Set selection and remove tmpNode
18047 						rng.setStart(node, 0);
18048 						selection.setRng(rng);
18049 						dom.remove(tmpNode);
18050 
18051 						return;
18052 					}
18053 				}
18054 			}
18055 		};
18056 	};
18057 })(tinymce);
18058 
18059 tinymce.onAddEditor.add(function(tinymce, ed) {
18060 	var filters, fontSizes, dom, settings = ed.settings;
18061 
18062 	function replaceWithSpan(node, styles) {
18063 		tinymce.each(styles, function(value, name) {
18064 			if (value)
18065 				dom.setStyle(node, name, value);
18066 		});
18067 
18068 		dom.rename(node, 'span');
18069 	};
18070 
18071 	function convert(editor, params) {
18072 		dom = editor.dom;
18073 
18074 		if (settings.convert_fonts_to_spans) {
18075 			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
18076 				filters[node.nodeName.toLowerCase()](ed.dom, node);
18077 			});
18078 		}
18079 	};
18080 
18081 	if (settings.inline_styles) {
18082 		fontSizes = tinymce.explode(settings.font_size_legacy_values);
18083 
18084 		filters = {
18085 			font : function(dom, node) {
18086 				replaceWithSpan(node, {
18087 					backgroundColor : node.style.backgroundColor,
18088 					color : node.color,
18089 					fontFamily : node.face,
18090 					fontSize : fontSizes[parseInt(node.size, 10) - 1]
18091 				});
18092 			},
18093 
18094 			u : function(dom, node) {
18095 				replaceWithSpan(node, {
18096 					textDecoration : 'underline'
18097 				});
18098 			},
18099 
18100 			strike : function(dom, node) {
18101 				replaceWithSpan(node, {
18102 					textDecoration : 'line-through'
18103 				});
18104 			}
18105 		};
18106 
18107 		ed.onPreProcess.add(convert);
18108 		ed.onSetContent.add(convert);
18109 
18110 		ed.onInit.add(function() {
18111 			ed.selection.onSetContent.add(convert);
18112 		});
18113 	}
18114 });
18115 
18116 (function(tinymce) {
18117 	var TreeWalker = tinymce.dom.TreeWalker;
18118 
18119 	tinymce.EnterKey = function(editor) {
18120 		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18121 
18122 		function handleEnterKey(evt) {
18123 			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
18124 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
18125 
18126 			// Returns true if the block can be split into two blocks or not
18127 			function canSplitBlock(node) {
18128 				return node &&
18129 					dom.isBlock(node) &&
18130 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
18131 					!/^(fixed|absolute)/i.test(node.style.position) && 
18132 					dom.getContentEditable(node) !== "true";
18133 			};
18134 
18135 			// Renders empty block on IE
18136 			function renderBlockOnIE(block) {
18137 				var oldRng;
18138 
18139 				if (tinymce.isIE && dom.isBlock(block)) {
18140 					oldRng = selection.getRng();
18141 					block.appendChild(dom.create('span', null, '\u00a0'));
18142 					selection.select(block);
18143 					block.lastChild.outerHTML = '';
18144 					selection.setRng(oldRng);
18145 				}
18146 			};
18147 
18148 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
18149 			function trimInlineElementsOnLeftSideOfBlock(block) {
18150 				var node = block, firstChilds = [], i;
18151 
18152 				// Find inner most first child ex: <p><i><b>*</b></i></p>
18153 				while (node = node.firstChild) {
18154 					if (dom.isBlock(node)) {
18155 						return;
18156 					}
18157 
18158 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18159 						firstChilds.push(node);
18160 					}
18161 				}
18162 
18163 				i = firstChilds.length;
18164 				while (i--) {
18165 					node = firstChilds[i];
18166 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
18167 						dom.remove(node);
18168 					}
18169 				}
18170 			};
18171 			
18172 			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
18173 			function moveToCaretPosition(root) {
18174 				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
18175 
18176 				rng = dom.createRng();
18177 
18178 				if (root.hasChildNodes()) {
18179 					walker = new TreeWalker(root, root);
18180 
18181 					while (node = walker.current()) {
18182 						if (node.nodeType == 3) {
18183 							rng.setStart(node, 0);
18184 							rng.setEnd(node, 0);
18185 							break;
18186 						}
18187 
18188 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18189 							rng.setStartBefore(node);
18190 							rng.setEndBefore(node);
18191 							break;
18192 						}
18193 
18194 						lastNode = node;
18195 						node = walker.next();
18196 					}
18197 
18198 					if (!node) {
18199 						rng.setStart(lastNode, 0);
18200 						rng.setEnd(lastNode, 0);
18201 					}
18202 				} else {
18203 					if (root.nodeName == 'BR') {
18204 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
18205 							// Trick on older IE versions to render the caret before the BR between two lists
18206 							if (!documentMode || documentMode < 9) {
18207 								tempElm = dom.create('br');
18208 								root.parentNode.insertBefore(tempElm, root);
18209 							}
18210 
18211 							rng.setStartBefore(root);
18212 							rng.setEndBefore(root);
18213 						} else {
18214 							rng.setStartAfter(root);
18215 							rng.setEndAfter(root);
18216 						}
18217 					} else {
18218 						rng.setStart(root, 0);
18219 						rng.setEnd(root, 0);
18220 					}
18221 				}
18222 
18223 				selection.setRng(rng);
18224 
18225 				// Remove tempElm created for old IE:s
18226 				dom.remove(tempElm);
18227 
18228 				viewPort = dom.getViewPort(editor.getWin());
18229 
18230 				// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
18231 				y = dom.getPos(root).y;
18232 				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
18233 					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
18234 				}
18235 			};
18236 
18237 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
18238 			// This function will also copy any text formatting from the parent block and add it to the new one
18239 			function createNewBlock(name) {
18240 				var node = container, block, clonedNode, caretNode;
18241 
18242 				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18243 				caretNode = block;
18244 
18245 				// Clone any parent styles
18246 				if (settings.keep_styles !== false) {
18247 					do {
18248 						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
18249 							clonedNode = node.cloneNode(false);
18250 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
18251 
18252 							if (block.hasChildNodes()) {
18253 								clonedNode.appendChild(block.firstChild);
18254 								block.appendChild(clonedNode);
18255 							} else {
18256 								caretNode = clonedNode;
18257 								block.appendChild(clonedNode);
18258 							}
18259 						}
18260 					} while (node = node.parentNode);
18261 				}
18262 
18263 				// BR is needed in empty blocks on non IE browsers
18264 				if (!tinymce.isIE) {
18265 					caretNode.innerHTML = '<br>';
18266 				}
18267 
18268 				return block;
18269 			};
18270 
18271 			// Returns true/false if the caret is at the start/end of the parent block element
18272 			function isCaretAtStartOrEndOfBlock(start) {
18273 				var walker, node, name;
18274 
18275 				// Caret is in the middle of a text node like "a|b"
18276 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
18277 					return false;
18278 				}
18279 
18280 				// If after the last element in block node edge case for #5091
18281 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
18282 					return true;
18283 				}
18284 
18285 				// If the caret if before the first element in parentBlock
18286 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
18287 					return true;
18288 				}
18289 
18290 				// Caret can be before/after a table
18291 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
18292 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
18293 				}
18294 
18295 				// Walk the DOM and look for text nodes or non empty elements
18296 				walker = new TreeWalker(container, parentBlock);
18297 	
18298 				// If caret is in beginning or end of a text block then jump to the next/previous node
18299 				if (container.nodeType == 3) {
18300 					if (start && offset == 0) {
18301 						walker.prev();
18302 					} else if (!start && offset == container.nodeValue.length) {
18303 						walker.next();
18304 					}
18305 				}
18306 
18307 				while (node = walker.current()) {
18308 					if (node.nodeType === 1) {
18309 						// Ignore bogus elements
18310 						if (!node.getAttribute('data-mce-bogus')) {
18311 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
18312 							name = node.nodeName.toLowerCase();
18313 							if (nonEmptyElementsMap[name] && name !== 'br') {
18314 								return false;
18315 							}
18316 						}
18317 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
18318 						return false;
18319 					}
18320 
18321 					if (start) {
18322 						walker.prev();
18323 					} else {
18324 						walker.next();
18325 					}
18326 				}
18327 
18328 				return true;
18329 			};
18330 
18331 			// Wraps any text nodes or inline elements in the specified forced root block name
18332 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
18333 				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
18334 
18335 				// Not in a block element or in a table cell or caption
18336 				parentBlock = dom.getParent(container, dom.isBlock);
18337 				if (!parentBlock || !canSplitBlock(parentBlock)) {
18338 					parentBlock = parentBlock || editableRoot;
18339 
18340 					if (!parentBlock.hasChildNodes()) {
18341 						newBlock = dom.create(blockName);
18342 						parentBlock.appendChild(newBlock);
18343 						rng.setStart(newBlock, 0);
18344 						rng.setEnd(newBlock, 0);
18345 						return newBlock;
18346 					}
18347 
18348 					// Find parent that is the first child of parentBlock
18349 					node = container;
18350 					while (node.parentNode != parentBlock) {
18351 						node = node.parentNode;
18352 					}
18353 
18354 					// Loop left to find start node start wrapping at
18355 					while (node && !dom.isBlock(node)) {
18356 						startNode = node;
18357 						node = node.previousSibling;
18358 					}
18359 
18360 					if (startNode) {
18361 						newBlock = dom.create(blockName);
18362 						startNode.parentNode.insertBefore(newBlock, startNode);
18363 
18364 						// Start wrapping until we hit a block
18365 						node = startNode;
18366 						while (node && !dom.isBlock(node)) {
18367 							next = node.nextSibling;
18368 							newBlock.appendChild(node);
18369 							node = next;
18370 						}
18371 
18372 						// Restore range to it's past location
18373 						rng.setStart(container, offset);
18374 						rng.setEnd(container, offset);
18375 					}
18376 				}
18377 
18378 				return container;
18379 			};
18380 
18381 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
18382 			function handleEmptyListItem() {
18383 				function isFirstOrLastLi(first) {
18384 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
18385 
18386 					// Find first/last element since there might be whitespace there
18387 					while (node) {
18388 						if (node.nodeType == 1) {
18389 							break;
18390 						}
18391 
18392 						node = node[first ? 'nextSibling' : 'previousSibling'];
18393 					}
18394 
18395 					return node === parentBlock;
18396 				};
18397 
18398 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
18399 
18400 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
18401 					// Is first and last list item then replace the OL/UL with a text block
18402 					dom.replace(newBlock, containerBlock);
18403 				} else if (isFirstOrLastLi(true)) {
18404 					// First LI in list then remove LI and add text block before list
18405 					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
18406 				} else if (isFirstOrLastLi()) {
18407 					// Last LI in list then temove LI and add text block after list
18408 					dom.insertAfter(newBlock, containerBlock);
18409 					renderBlockOnIE(newBlock);
18410 				} else {
18411 					// Middle LI in list the split the list and insert a text block in the middle
18412 					// Extract after fragment and insert it after the current block
18413 					tmpRng = rng.cloneRange();
18414 					tmpRng.setStartAfter(parentBlock);
18415 					tmpRng.setEndAfter(containerBlock);
18416 					fragment = tmpRng.extractContents();
18417 					dom.insertAfter(fragment, containerBlock);
18418 					dom.insertAfter(newBlock, containerBlock);
18419 				}
18420 
18421 				dom.remove(parentBlock);
18422 				moveToCaretPosition(newBlock);
18423 				undoManager.add();
18424 			};
18425 
18426 			// Walks the parent block to the right and look for BR elements
18427 			function hasRightSideBr() {
18428 				var walker = new TreeWalker(container, parentBlock), node;
18429 
18430 				while (node = walker.current()) {
18431 					if (node.nodeName == 'BR') {
18432 						return true;
18433 					}
18434 
18435 					node = walker.next();
18436 				}
18437 			}
18438 			
18439 			// Inserts a BR element if the forced_root_block option is set to false or empty string
18440 			function insertBr() {
18441 				var brElm, extraBr;
18442 
18443 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18444 					// Insert extra BR element at the end block elements
18445 					if (!tinymce.isIE && !hasRightSideBr()) {
18446 						brElm = dom.create('br')
18447 						rng.insertNode(brElm);
18448 						rng.setStartAfter(brElm);
18449 						rng.setEndAfter(brElm);
18450 						extraBr = true;
18451 					}
18452 				}
18453 
18454 				brElm = dom.create('br');
18455 				rng.insertNode(brElm);
18456 
18457 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18458 				if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18459 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18460 				}
18461 
18462 				if (!extraBr) {
18463 					rng.setStartAfter(brElm);
18464 					rng.setEndAfter(brElm);
18465 				} else {
18466 					rng.setStartBefore(brElm);
18467 					rng.setEndBefore(brElm);
18468 				}
18469 
18470 				selection.setRng(rng);
18471 				undoManager.add();
18472 			};
18473 
18474 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
18475 			function trimLeadingLineBreaks(node) {
18476 				do {
18477 					if (node.nodeType === 3) {
18478 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
18479 					}
18480 
18481 					node = node.firstChild;
18482 				} while (node);
18483 			};
18484 
18485 			function getEditableRoot(node) {
18486 				var root = dom.getRoot(), parent, editableRoot;
18487 
18488 				// Get all parents until we hit a non editable parent or the root
18489 				parent = node;
18490 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
18491 					if (dom.getContentEditable(parent) === "true") {
18492 						editableRoot = parent;
18493 					}
18494 
18495 					parent = parent.parentNode;
18496 				}
18497 				
18498 				return parent !== root ? editableRoot : root;
18499 			};
18500 
18501 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
18502 			function addBrToBlockIfNeeded(block) {
18503 				var lastChild;
18504 
18505 				// IE will render the blocks correctly other browsers needs a BR
18506 				if (!tinymce.isIE) {
18507 					block.normalize(); // Remove empty text nodes that got left behind by the extract
18508 
18509 					// Check if the block is empty or contains a floated last child
18510 					lastChild = block.lastChild;
18511 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
18512 						dom.add(block, 'br');
18513 					}
18514 				}
18515 			};
18516 
18517 			// Delete any selected contents
18518 			if (!rng.collapsed) {
18519 				editor.execCommand('Delete');
18520 				return;
18521 			}
18522 
18523 			// Event is blocked by some other handler for example the lists plugin
18524 			if (evt.isDefaultPrevented()) {
18525 				return;
18526 			}
18527 
18528 			// Setup range items and newBlockName
18529 			container = rng.startContainer;
18530 			offset = rng.startOffset;
18531 			newBlockName = settings.forced_root_block;
18532 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
18533 			documentMode = dom.doc.documentMode;
18534 
18535 			// Resolve node index
18536 			if (container.nodeType == 1 && container.hasChildNodes()) {
18537 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18538 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18539 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
18540 					offset = container.nodeValue.length;
18541 				} else {
18542 					offset = 0;
18543 				}
18544 			}
18545 
18546 			// Get editable root node normaly the body element but sometimes a div or span
18547 			editableRoot = getEditableRoot(container);
18548 
18549 			// If there is no editable root then enter is done inside a contentEditable false element
18550 			if (!editableRoot) {
18551 				return;
18552 			}
18553 
18554 			undoManager.beforeChange();
18555 
18556 			// If editable root isn't block nor the root of the editor
18557 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
18558 				if (!newBlockName || evt.shiftKey) {
18559 					insertBr();
18560 				}
18561 
18562 				return;
18563 			}
18564 
18565 			// Wrap the current node and it's sibling in a default block if it's needed.
18566 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
18567 			// This won't happen if root blocks are disabled or the shiftKey is pressed
18568 			if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
18569 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
18570 			}
18571 
18572 			// Find parent block and setup empty block paddings
18573 			parentBlock = dom.getParent(container, dom.isBlock);
18574 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
18575 
18576 			// Setup block names
18577 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18578 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18579 
18580 			// Handle enter inside an empty list item
18581 			if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
18582 				// Let the list plugin or browser handle nested lists for now
18583 				if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
18584 					return false;
18585 				}
18586 
18587 				handleEmptyListItem();
18588 				return;
18589 			}
18590 
18591 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
18592 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
18593 				if (!evt.shiftKey) {
18594 					insertBr();
18595 					return;
18596 				}
18597 			} else {
18598 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
18599 				if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
18600 					insertBr();
18601 					return;
18602 				}
18603 			}
18604 
18605 			// Default block name if it's not configured
18606 			newBlockName = newBlockName || 'P';
18607 
18608 			// Insert new block before/after the parent block depending on caret location
18609 			if (isCaretAtStartOrEndOfBlock()) {
18610 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
18611 				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
18612 					newBlock = createNewBlock(newBlockName);
18613 				} else {
18614 					newBlock = createNewBlock();
18615 				}
18616 
18617 				// Split the current container block element if enter is pressed inside an empty inner block element
18618 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
18619 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
18620 					newBlock = dom.split(containerBlock, parentBlock);
18621 				} else {
18622 					dom.insertAfter(newBlock, parentBlock);
18623 				}
18624 
18625 				moveToCaretPosition(newBlock);
18626 			} else if (isCaretAtStartOrEndOfBlock(true)) {
18627 				// Insert new block before
18628 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
18629 				renderBlockOnIE(newBlock);
18630 			} else {
18631 				// Extract after fragment and insert it after the current block
18632 				tmpRng = rng.cloneRange();
18633 				tmpRng.setEndAfter(parentBlock);
18634 				fragment = tmpRng.extractContents();
18635 				trimLeadingLineBreaks(fragment);
18636 				newBlock = fragment.firstChild;
18637 				dom.insertAfter(fragment, parentBlock);
18638 				trimInlineElementsOnLeftSideOfBlock(newBlock);
18639 				addBrToBlockIfNeeded(parentBlock);
18640 				moveToCaretPosition(newBlock);
18641 			}
18642 
18643 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
18644 			undoManager.add();
18645 		}
18646 
18647 		editor.onKeyDown.add(function(ed, evt) {
18648 			if (evt.keyCode == 13) {
18649 				if (handleEnterKey(evt) !== false) {
18650 					evt.preventDefault();
18651 				}
18652 			}
18653 		});
18654 	};
18655 })(tinymce);
18656 
18657